Windows CE SDK Programmer's Guide

Windows CE Platform SDK

The Microsoft Windows CE operating system is the portable, real-time, modular operating system for embedded and dedicated systems. The Platform SDK contains the tools and information you need to develop applications for Windows CE-based platforms.

For the latest information about developing for Windows CE, visit the Microsoft Windows CE World Wide Web site, http://www.microsoft.com/ windowsce/developer.

Windows CE Programmer's Guide
How to write applications for Windows CE.

Windows CE Reference
Documentation for Windows CE programming interfaces.

Getting Started with the Windows CE Device Driver Kit
Information and resources necessary to build device drivers for installable devices.


Legal Information

Information in this document is subject to change without notice. The example companies, organizations, products, people, and events depicted herein are fictitious. No association with any real company, organization, product, person, or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in, or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation.

Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property.

©1998 Microsoft Corporation. All rights reserved.

Microsoft, Microsoft Press, MS, MS-DOS, Visual Basic, Windows, Win32, Win32s, Visual C++, and Windows NT are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries.

Adaptec is a registered trademark of Adaptec, Inc.

Macintosh and TrueType are registered trademarks of Apple Computer, Inc.

Asymetrix and ToolBook are registered trademarks of Asymetrix Corporation.

CompuServe is a registered trademark of CompuServe, Inc.

Sound Blaster and Sound Blaster Pro are trademarks of Creative Technology, Ltd.

Alpha AXP and DEC are trademarks of Digital Equipment Corporation.

Kodak is a registered trademark of Eastman Kodak Company.

PANOSE is a trademark of ElseWare Corporation.

Future Domain is a registered trademark of Future Domain Corporation.

Hewlett-Packard, HP, LaserJet, and PCL are registered trademarks of Hewlett-Packard Company.

AT, IBM, Micro Channel, OS/2, and XGA are registered trademarks, and PC/XT and RISC System/6000 are trademarks of International Business Machines Corporation.

Intel and Pentium are registered trademarks, and i386 and i486 are trademarks of Intel Corporation.

Video Seven is a trademark of Headland Technology, Inc.

Lotus is a registered trademark of Lotus Development Corporation.

MIPS is a registered trademark of MIPS Computer Systems, Inc.

Arial, Monotype, and Times New Roman are registered trademarks of The Monotype Corporation.

Motorola is a registered trademark of Motorola, Inc.

NCR is a registered trademark of NCR Corporation.

Nokia is a registered trademark of Nokia Corporation.

Novell and NetWare are registered trademarks of Novell, Inc.

Olivetti is a registered trademark of Ing. C. Olivetti.

PostScript is a registered trademark of Adobe Systems, Inc.

R4000 is a trademark of MIPS Computer Systems, Inc.

Roland is a registered trademark of Roland Corporation.

SCSI is a registered trademark of Security Control Systems, Inc.

Epson is a registered trademark of Seiko Epson Corporation, Inc.

Silicon Graphics is a registered trademark and OpenGL is a trademark of Silicon Graphics, Inc.

Stacker is a registered trademark of STAC Electronics.

Sun Microsystems is a registered trademark of Sun Microsystems, Inc.

Tandy is a registered trademark of Tandy Corporation.

Unicode is a registered trademark of Unicode, Incorporated.

UNIX is a registered trademark in the United States and other countries, licensed exclusively through X/Open Company, Ltd.

VAX is a trademark of Digital Equipment Corporation

Yamaha is a registered trademark of Yamaha Corporation of America.

Paintbrush is a trademark of Wordstar Atlanta Technology Center.

The names of actual companies and products mentioned herein may be the trademarks of their respective owners.


Overview of the Windows CE Operating System

Microsoft® Windows® CE is a compact, highly efficient, multiplatform operating system. It is not a reduced version of Microsoft® Windows® 95, but was designed from the ground up as a multithreaded, fully preemptive, multitasking operating system for platforms with limited resources. Its modular design allows it to be customized for products ranging from consumer electronic devices to specialized industrial controllers.

General Features of Windows CE

  • Provides you with a modular operating system that you can customize for specific products. The basic core of the operating system requires less than 200 KB of ROM.
  • Provides interrupt delivery, prioritizing, and servicing.
  • Runs on a wide variety of platforms.
  • Supports more than 1,000 of the most frequently used Microsoft® Win32® functions, along with familiar development models and tools.
  • Supports a variety of user-interface hardware, including touch screen and color displays with up to 32-bits-per-pixel color depth.
  • Supports a variety of serial and network communication technologies.
  • Supports Mobile Channels to provide Web services for Windows CE users.
  • Supports COM/OLE, Automation, and other advanced methods of interprocess communication.

Windows CE has four primary modules or groups of modules.

  • The kernel supports basic services, such as process and thread handling and memory management.
  • The file system supports persistent storage of information.
  • The Graphics, Windowing, and Events Subsystem (GWES) controls graphics and window-related features.
  • The communications interface supports the exchange of information with other devices.

The Windows CE operating system also contains a number of additional modules that support such tasks as managing installable device drivers and supporting COM/OLE. The following illustration shows how these features fit into the overall structure of the Windows CE operating system.

Windows CE operating system structure

Windows CE supports the following CPUs:

AMD X5 | MIPS R4101
ARM 720T | Motorola MPC823
ARM SA-1100 | NEC VR4111 (16 bit support)
Hitachi SH4 (16 bit support) | NEC VR4300
IBM PPC 403GC | PPC 821
MIPS 4102 | QED5230
MIPS R3910 | SH3
MIPS R3912 | x86


Summary of the Windows CE Operating System

The following section outlines the major features of Windows CE. The features mentioned are discussed in more detail throughout the chapter.

Kernel

The Kernel — the core of the operating system — provides system services for managing threads, memory, and resources. It includes:

  • Preemptive, priority-based thread scheduling based on the Win32 process and thread model. Priority inversion is prevented with a system of priority inheritance that dynamically adjusts thread priorities.
  • Predictable thread synchronization mechanisms, including wait objects. Examples of these mechanisms are named mutexes, critical sections, and named and unnamed event objects.
  • Efficient memory management based on dynamic-link libraries (DLLs), which link user applications at run-time.
  • A flat, virtual address space, with 32 MB of memory reserved for each process. Process memory is protected by altering page protections.
  • On-demand paging for bothROM and RAM.
  • Heap size that is limited only by available memory.
  • Control of interrupt handling. You can map interrupt requests (IRQs) to hardware interrupts and implement your own interrupt service routines and interrupt service threads.
  • Extensive debugging support, such as including just-in-time debugging.

Persistent Storage

The file system supports persistent storage of information. It includes:

  • Support for file allocation table (FAT) file systems with up to nine FAT volumes.
  • Transactioned file handling to protect against data loss.
  • Demand paging for devices that support paging.
  • FAT file system mirroring to allow preservation of the file system if power is lost or cold reset is needed.
  • Installable block device drivers.

Communications Interface

The communications interface supports a wide range of technologies. It includes:

  • Support for serial communications, including infrared links.
  • Support for Internet client applications, including Hypertext Transfer Protocol (HTTP) and File Transfer Protocol (FTP) protocols.
  • A Common Internet File System (CIFS) redirector for access to remote file systems by means of the Internet.
  • A subset of Windows Sockets (Winsock) version 1.1, plus support for Secure Sockets.
  • A Transmission Control Protocol/Internet Protocol (TCP/IP) transport layer configurable for wireless networking.
  • An Infrared Data Association (IrDA) transport layer for robust infrared communication.
  • Both Point-to-Point Protocol (PPP) and Serial Line Internet Protocol (SLIP) for serial-link networking.
  • Support for local area networking through the network driver interface specification (NDIS).
  • Support for managing phone connections with the Telephony API (TAPI).
  • A Remote Access Service (RAS) client for connections to remote file systems by modem.

Graphics, Windowing, and Events Subsystem (GWES)

The GWES module supports the graphics and windowing functionality needed to display text and images and to receive user input. It includes:

  • Support for a broad range of window styles, including overlapping windows.
  • A large selection of customizable controls.
  • Support for keyboard and stylus input.
  • A command bar combining the functionality of a toolbar and a menu bar.
  • An Out of Memory dialog box that requests user action when the system is low on memory.
  • Full UNICODE support.
  • A multiplatform graphics device interface (GDI) that supports the following features:
    • Both color and grayscale displays, with color depths of up to 32 bits per pixel.
    • Palette management.
    • TrueType and raster fonts.
    • Printer, memory, and display device contexts.
    • Advanced shape drawing and bit block transfer capabilities.


Kernel

The Windows CE kernel contains the core operating system functionality that must be present on all Windows CE-based platforms. It includes support for memory management, process management, exception handling, multitasking, and multithreading.

The Windows CE kernel borrows much of what is best from Windows-based desktop platforms. For example, all Windows CE-based applications run in a fully preemptive, multitasking environment, in protected memory spaces. Windows CE supports native Unicode strings, allowing you to internationalize applications.

Unlike the kernels found on Windows-based desktop platforms, the Windows CE kernel uses DLLs to maximize available memory. The DLLs are written as reentrant code, which allows applications to simultaneously share common routines. This approach minimizes the amount of memory-resident code required to execute applications.


Processes and Threads

As a multitasking operating system, Windows CE can support up to 32 simultaneous processes, each process being a single instance of an application. In addition, multithreading support allows each process to create multiple threads of execution. A thread is a part of a process that runs concurrently with other parts. Threads operate independently, but each one belongs to a particular process and shares the same memory space. The total number of threads is limited only by available physical memory.

Processes rely on Win32 messages to initiate processing, control system resources, and communicate with the operating system and the user. Each process has its own message queue. For multithreaded applications, each thread also has its own separate message queue. When there are no messages in the queue and the thread is not engaged in any other activity, the system suspends the thread, saving CPU resources.

Although a thread can operate independently, it often needs to be managed by the process. For example, one thread may depend on another for information. Thread synchronization suspends a thread’s execution until the thread receives notification to proceed. Windows CE supports thread synchronization by providing a set of wait objects, which stops a thread until a change in the wait object signals the thread to proceed. Supported wait objects include critical sections, named and unnamed events, and named mutex objects. For more information, see Working with Processes and Threads.

Windows CE implements thread synchronization with minimum processor resources — an important feature for many battery-powered devices. And, unlike many operating systems, Windows CE uses the kernel to handle thread-related tasks, such as scheduling, synchronization, and resource management. Consequently, an application need not poll for process or thread completion or perform other thread-management functions.

Because Windows CE is preemptive, it allows the execution of a process or thread to be preempted by one with higher priority. It uses a priority-based, time-slice algorithm, with eight levels of thread priority, for thread scheduling.


Interrupt Handling

To provide efficient processing of interrupts, Windows CE splits interrupt handling into two distinct parts: an interrupt service routine (ISR) and an interrupt service thread (IST). When triggered, the ISR does little more than launch the IST that is responsible for handling the event. Once the IST has been launched, the ISR returns and the system can respond to the next interrupt.

Dividing interrupt handling this way allows the ISR to be very small and fast. This minimizes interrupt latencies and speeds interrupt processing. The Windows CE Platform Builder makes it possible for you to specify interrupt timing and priorities for a specific platform.


Memory Architecture

The Windows CE kernel supports a single, flat, or unsegmented, virtual address space that all processes share. Instead of assigning each process a different address space, Windows CE protects process memory by altering page protections. Because it maps virtual addresses onto physical memory using the kernel, you do not need to be concerned with the physical layout of the target system’s memory.

Approximately 1 GB of virtual memory is available to processes. It is divided into 33 slots, each 32 MB in size. The kernel protects each process by assigning it to a unique slot with one slot reserved for the currently running process. Thus, the number of processes is limited to 32, but there is no limit, aside from physical memory, on the total number of threads.

The kernel prevents an application from accessing memory outside of its allocated slot by generating an exception. Applications can check for, and handle, such exceptions by using the try-except statement.

Windows CE allows memory mapping, which permits multiple processes to share the same physical memory. Memory mapping results in very fast data transfer between cooperating processes, or between a driver and an application. Approximately 1 GB of virtual address space, distinct from that used for the slots, is allocated for memory mapping.

Windows CE always allocates memory to applications one page at a time. The system designer specifies page size when the operating system is built for the target hardware platform. On a Handheld PC (H/PC), for example, the page size is typically either 1 KB or 4 KB.


Physical Memory Use

Windows CE-based platforms usually have no disk drive. Therefore, physical memory, typically consisting of a combination of ROM and RAM, plays a substantially different role on a Windows CE-based platform than it does on a desktop computer.

Because ROM cannot be modified by the user, it is used for permanent storage. The contents of ROM, determined by the original equipment manufacturer (OEM), includes the operating system and any built-in applications that the manufacturer provides, for example, Microsoft® Pocket Word and Microsoft® Pocket Excel on an H/PC. Depending on your product requirements, you can also place application code in ROM.

Because on most Windows CE systems, RAM is maintained continuously, it is effectively nonvolatile. This feature allows your application to use RAM for persistent storage as well as application execution, compensating for the lack of a disk drive. To serve these two purposes, RAM is divided into storage, also known as the object store, and program memory. Program memory is used for application execution, while the object store is used for persistent storage of data and any executable code not stored in ROM.

To minimize RAM requirements on Windows CE-based devices, executable code stored in ROM usually executes in-place, not in RAM. Because of this, the operating system needs only a small amount of RAM for such purposes as stack and heap storage.

Applications are commonly stored and executed in RAM. This approach is used primarily by third-party applications that are added by the user. Because RAM-based applications are stored in compressed form, they must be uncompressed and loaded into program memory for execution. To increase the performance of application software and reduce RAM use, Windows CE supports on-demand paging. With it, the operating system needs to uncompress and load only the memory page containing the portion of the application that is currently executing. When execution is finished, the page can be swapped out, and the next page can be loaded.

Like RAM-based applications, ROM-based executable code, including DLLs, can be compressed. When compressed, the code does not execute in place, but is handled much like its RAM-based counterpart. The code is uncompressed and loaded a page at a time into RAM program memory, and then is swapped out when no longer needed.


Persistent Storage

The storage memory portion of RAM is referred to as the object store. It includes these types of data storage:

  • The Windows CE file system, which contains application and data files.
  • The Windows CE database, which provides structured storage. It offers an alternative to storing user data and application data in files or in the registry.
  • The Windows CE system registry, which is used to store the system configuration and any other information that an application must access quickly.

The Windows CE file system holds executable files and data files that the user installs or creates. It supports up to nine FAT volumes. Each volume is treated as a storage card. If a storage card has multiple partitions, then each partition is treated as a separate volume. It is possible to support other types of file systems by writing block device drivers. For more information on block device drivers, see the documentation for the Windows CE Device Driver Kit (DDK). Files are typically stored in compressed form. Applications access the file system with standard Win32 file system functions. For more information about file management, see Accessing Persistent Storage.

To reduce the data loss during a critical failure, such as loss of power, the Windows CE file system is transactioned. In addition, the file system implements a transactioned mirroring scheme to track FAT file system operations that are not transactioned. The mirroring scheme restores the FAT volume if power is lost while a critical operation is performed.

The Windows CE database provides general-purpose, structured storage of data, but it is not a full-fledged database. In particular, Windows CE databases have only one level of hierarchy. Records cannot contain other records, nor can they be shared between databases. For more information about storage, see Accessing Persistent Storage.

Platforms that implement the Windows CE operating system commonly ship with one or more built-in databases. For example, the H/PC comes with calendar and contacts applications that use databases to store their information. Windows CE provides an API that allows users and applications to create and use additional databases as needed.

The system registry is used to store a variety of information, such as system or application configuration data. It is similar to the registry found on Windows-based desktop platforms. Applications running on a Windows CE-based device can access and modify information in the registry with standard Win32 functions.

It is also possible to access the Windows CE object store from an attached desktop computer using one of the following approaches:

  • The Windows CE remote API (RAPI) includes a set of functions that you can use to manipulate the file system and the registry. An application running on the desktop computer invokes RAPI functions, which act on the object store of the linked Windows CE-based device. For more information, see Invoking Functions from a Desktop Computer.
  • Windows CE offers an extensive and sophisticated set of tools for transferring data between a desktop computer and an attached Windows CE-based device. For example, you can use these tools to synchronize data between a Windows CE-based device and an attached desktop computer when you update a list of contacts. For more information, see Synchronizing Data.
  • The object store can also be modified when installing an application. For more information, see Installing and Managing Applications.


Communications Interface

Windows CE-based platforms support a variety of communications hardware and data protocols, including serial I/O support, such as infrared transceiver support; a subset of the TAPI; and networking. The following networking options are supported:

  • Internet clients, including HTTP and FTP, and Internet security protocols.
  • Access to remote file systems through a CIFS redirector.
  • Internet Control Message Protocol (ICMP) messaging support.
  • A subset of Winsock version 1.1, including support for security protocols.
  • A RAS client.
  • TCP/IP and IrDA.
  • NDIS for local area networking.
  • PPP and SLIP for serial link and modem networking.


Serial Communications

Serial I/O is the simplest form of communication supported by Windows CE. It is used when there is a direct, one-to-one connection between two devices. It can take place over a variety of hardware connections, but most Windows CE-based devices use simple serial cables or infrared transceivers. Transferring information over a serial cable connection is similar to reading from or writing to a file, and it uses some of the same functions. Windows CE also includes a set of functions used to manage the connection itself.

Windows CE allows direct serial I/O over an infrared link using the same serial communication functions that are used for wired connections. When using an infrared link, the I/O is “raw,” which means that the bit stream is not processed in any way. For example, there is no collision detection in Windows CE.

The IrDA protocols provide more robust communication than raw infrared (IR). The IrDA protocols are available through Infrared Sockets (IrSock), which is an extension of Winsock. As an alternative to using IrSock directly, IrComm uses the same function calls as standard serial communication, but uses IrSock and the IrDA protocols internally.


Network Communications

Windows CE supports a network stack with a number of options. Network communications can take place over a variety of hardware, including infrared, serial, Ethernet, and wireless links. Although the network stack is accessible only through the Winsock interface, Windows CE also provides several high-level APIs that use Winsock internally, and handle the details of setting up and managing socket connections:

  • The Windows CE Internet API (WinInet) provides support for Internet browsing protocols, including FTP and HTTP 1.0. It also provides access to three Internet security protocols, Secure Sockets Layer (SSL) 2.0, Secure Sockets Layer 3.0, and Private Communication Technology 1.0.
  • The Windows CE networking API (WNET) provides access to remote file systems through a Common Internet File System (CIFS) redirector. Currently, operating system connections are supported only for Microsoft® Windows® 95 and Microsoft® Windows NT®. The redirector supports UNC names, such as //serverXX/shareXX, but not drive letters.
  • ICMP requests, commonly referred to as pings, are used to determine whether or not a host is available.

The Winsock interface provides direct access to the network stack. Windows CE supports a subset of Winsock 1.1, as well as the three Internet security protocols mentioned earlier. For infrared communication, Windows CE supports the IrSock extension of Winsock, which enables socket-based infrared communication using the industry-standard IrDA protocols.

Windows CE supports a RAS client at the same level in the network stack as Winsock, but this client serves a different purpose. RAS is a multi-protocol router used to connect remote devices. The Windows CE RAS client is identical to the Win32 RAS, except it supports only one point-to-point connection at a time.

The TCP/IP suite, developed for the Internet, is arguably the most flexible and widely implemented network protocol. It is supported by a wide variety of systems and forms the core of the Windows CE network stack. Many Windows CE-based mobile devices have wireless communication capabilities. However, conventional TCP/IP stacks may work poorly with wireless technology because they were intended to function efficiently on wired networks. The Windows CE TCP/IP stack is designed to be configured for wireless networking.

At the base of the network stack, Windows CE supports data-link layers for serial-link networks and local area networks (LANs). Many Windows CE-based devices connect to a network using a serial communication link, such as a modem. To support serial-link networking, Windows CE implements the widely used serial line Internet (SLIP) and Point-to-Point Protocol (PPP). Three protocols are available for authentication in serial-link communication: Password Authentication Protocol (PAP), Challenge Handshake Authentication Protocol (CHAP), and Microsoft CHAP.

To support connections to LANs, Windows CE implements NDIS 4.0, but supports only Ethernet miniport drivers. Windows CE does not support wide area networks (WANs). For more information on NDIS support, see the documentation for the Windows CE DDK.


Telephony API

Using a modem involves making and managing a telephone connection, tasks which fall outside most standard communication protocols. To facilitate the use of a telephone connection, Windows CE includes a TAPI service provider for an AT command-based modem, knows as a Unimodem. TAPI is a collection of utilities that allows applications to take advantage of a variety of telephone and communications services without needing detailed knowledge of the particular technology. You can use the TAPI service provider with internal or peripheral modems.

The Windows CE implementation of TAPI focuses on outgoing calls and provides outbound dialing and address translation services. Windows CE does not support inbound calls. To provide flexibility in the choice of hardware, TAPI supports installable service providers.

“Wireless Services for Windows CE.” For more information, see Wireless Services for Windows CE. ›


Graphics, Windowing, and Events Subsystem

The Graphics Windowing and Events Subsystem (GWES) is the graphical user interface between the user, your application, and the operating system. GWES handles user input by translating keystrokes, stylus movements, and control selections into messages that convey information to applications and the operating system. GWES handles output to the user by creating and managing the windows, graphics, and text that are displayed on display devices and printers.

GWES supports all the windows, dialog boxes, controls, menus, and resources that make up Windows CE user interface. This interface allows users to control applications by choosing menu commands, pushing buttons, checking and unchecking boxes, and manipulating a variety of other controls. GWES provides information to the user in the form of bitmaps, carets, cursors, text, and icons.

Even Windows CE-based platforms that lack a graphical user interface use GWES basic windowing and messaging capabilities. These provide the means for communication between the user, the application, and the operating system.

As part of GWES, Windows CE provides support for active power management to extend the limited lifetime of battery-operated devices. The operating system automatically determines a power consumption level to match the state of operation of the device.

The following illustration shows the basic GWES structure.

GWES structure


Window Management

The most central feature of GWES is the window. In Windows CE-based platforms with traditional graphical displays, the window is the rectangular area of the screen where an application displays output and receives input from the user. However, all applications need windows in order to receive messages from the operating system, even those created for devices that lack graphical displays.

When you create a window, Windows CE creates a message queue for the window. The operating system translates the information it receives from the user into messages which it places into the message queue of the active window. The application processes most of these messages, and passes the rest back to Windows CE for processing.

Windows CE does not send applications any messages dealing with the nonclient area of the window. A window’s nonclient area is the area of the window where an application is not allowed to draw, such as the title bar and scroll bars. The window manager controls the nonclient area.

Windows CE does not support the Maximize and Minimize buttons. A user can send the window to the back of the Z order by tapping the window’s button on the taskbar. The user restores the window by tapping its taskbar button again.

The taskbar is always visible on Windows CE. You cannot hide the taskbar or use the full screen to display a window.


Controls, Menus, Dialog Boxes, and Resources

GWES provides controls, menus, dialog boxes, and resources to provide the user with a standard way to make selections, carry out commands, and perform input and output tasks.

Controls and dialog boxes are child windows that allow users to view and organize information and to set or change attributes. A dialog box is a window that contains controls.

All menus in Windows CE are implemented as top-level, pop-up windows. Windows CE supports scrolling menus that automatically add scroll arrows when a menu does not fit on the screen.

Windows CE does not support menu bars, but it does support command bars, which combine the functionality of a menu bar and tool bar in one control. Command bars make efficient use of the limited space available on many Windows CE-based devices.

Windows CE supports the following types of controls, menus, dialog boxes, and resources:

Application -defined dialog boxes | Bitmaps
Carets | Check boxes
Combo boxes | Command band
Command bars | Common dialog boxes
Cursors | Custom draw service
Date and time picker controls | Edit control
Group boxes | Header controls
Icons | Image lists
Images | Keyboard accelerators
List boxes | List views
Menus | Message boxes
Month calendar controls | Progress bars
Property sheets | Push buttons
Radio buttons | Rebars
Scroll bars | Static controls
Status bars | Strings
Tab controls | Toolbars
ToolTips | Track bars
Tree views | Up-down controls

In addition to the controls listed in the previous table, Windows CE supports the HTML viewer control, which makes it easier for you to add HTML support to your applications.


Graphics Device Interface

The graphics device interface (GDI) is the GWES subsystem that controls the display of text and graphics. You use GDI to draw lines, curves, closed figures, text, and bitmapped images.

GDI uses a device context to store the information it needs to display text and graphics on a specified device. The graphic objects stored in a device context include a pen for line drawing, a brush for painting and filling, a font for text output, a bitmap for copying or scrolling, a palette for defining the available colors, and a region for clipping. Windows CE supports printer device contexts for drawing on printers, display device contexts for drawing on video displays, and memory displays for drawing in memory.

The following table shows GDI features that are supported by Windows CE.

GDI feature
Description
Raster and TrueType fonts | Allows only one of these to be used on a specified system. TrueType fonts are scalable and rotatable.
Custom color palettes, and both palettized and nonpalettized color display devices | Supports color formats of 1, 2, 4, 8, 16, 24, and 32 bits per pixel (bpp). The 2 bpp format is unique to Windows CE.
Bit block transfer functions and raster operation codes | Allows you to transform and combine bitmaps in a wide variety of ways.
Pens and brushes | Supports dashed, wide, and solid pens, and patterned brushes.
Printing | Supports full graphical printing.
Shape drawing functions | Supports the ellipse, polygon, rectangle, and round rectangle shapes.


User Input

You can configure Windows CE to meet the user input requirements of a variety of different platforms. Currently, the keyboard, input panel, voice, and stylus are the usual input methods on Windows CE-based devices.

Keyboard functionality in Windows CE is similar to that of Windows-based desktop platforms. And, like those platforms, Windows CE supports hot keys. A hot key gives the user high-priority system access for specific purposes, such as canceling a time-consuming file transfer operation.

A unique feature of Windows CE is that it supports the use of a stylus and a touch screen in place of a mouse. Touching the screen with the stylus mimics the left-button mouse click.


Additional Support for Applications

Windows CE allows you to add several modules to facilitate application development or add additional programming capabilities.

The Microsoft Component Object Model (COM) is a powerful tool for object-oriented development. Windows CE provides a set of functions and structures designed to support application development based on COM. They can be divided into two groups:

  • COM

    A simple protocol that defines COM objects along with a library that offers object management services.

  • Automation

    A more sophisticated set of object management services that allows applications to coordinate their interactions with each other.

Both services are derived from a subset of the Windows NT object services. For more information on the use of COM/OLE, see the Microsoft Windows Platform SDK.

For Windows CE-based platforms intended to perform as adjuncts to a desktop computer, Windows CE provides the following tools to allow a user to manage and transfer data between a desktop computer and an attached Windows CE-based device:

  • A connection manager for establishing and maintaining the connection.
  • A data synchronization interface to allow synchronization of shared data.
  • File filters for importing and exporting files.
  • RAPI for enabling a client on a desktop computer to request services, such as file manipulation, from a server on an attached Windows CE-based device.
  • Application installation and management services for installing and uninstalling Windows CE-based applications from an attached desktop computer or other sources.

Windows CE-based devices may have one or more applications included on ROM. The Contacts and Inbox applications are of particular interest because they are open, general-purpose applications. If you have unique requirements, you can use these applications as processing engines inside proprietary code.

To aid you in implementing compatible applications, the Windows CE SDK provides the following functions and structures that a custom application can use to interface with the built-in Contacts and Inbox applications:

  • The Contacts database API, which supports functions and structures for querying and manipulating the records in a Contacts database. ·The mail interface API, which supports functions and structures that allow an application to be compatible with the Windows CE Inbox mail client. This API also provides services for mail transport and storage. ·The mail transport API, which allows applications to connect to the mail server and transmit and receive messages.

Most Windows CE-based devices have a shell to manage the user interface (UI) and handle such tasks as launching applications and switching between tasks. Because of the variety of Windows CE-based platforms, the operating system contains no standard shell; each platform has a shell designed for its particular needs. Refer to OEM documentation for information on the shell available for a particular platform. The shells implemented for a Palm-size PC and an H/PC are discussed in their respective platform-specific sections of this book.

Devices that are integral to a Windows CE-based platform, such as a wired serial port, have built-in drivers that are provided by the OEM. You can install other devices by means of connectors, such as built-in serial ports, PC Card or minicard sockets, or USB ports. Examples of installable devices include modems, printers, digital cameras, and bar code readers. Because these are added by the user, they require installable drivers.

The Win32 API provides a rich set of interface methods that make device drivers easier to write and more adaptable. You use the same functions whether you are dealing with a disk file, serial port, parallel port, pipe, or other type of device. Devices and files that must be accessed by multiple processes or threads simultaneously can be locked on a region-by-region basis. The Win32 API supports both synchronous and asynchronous methods of device access, and is designed with complex device interfaces in mind.

For more information on Windows CE support for installable device drivers, see the documentation for the Windows CE DDK.

To support development of devices and applications for a variety of locales, Windows CE includes national language support (NLS). The national language support API allows you to specify information about the system and the user locale. Support for localization of applications includes built-in support for Brazilian Portuguese, Dutch, French, German, Italian, Japanese, Spanish, and Swedish.


Working with Processes and Threads

When you start a Windows CE-based application, the operating system automatically creates a process and a primary thread for that process. A process is a single instance of a running application and a thread is the basic unit of execution. Windows CE-based applications can incorporate multiple processes and each process can contain multiple threads.

Every process has at least one thread. You create additional threads by calling the CreateThread function. You call the ExitThread function to free up the resources used by a thread when it is no longer needed. Calling Exit Thread for an application’s primary thread causes the application to terminate.

Note Unlike processes on Windows-based desktop platforms, a Windows CE process terminates if its primary thread is terminated, even if there are other active threads in existence for the process.

Consistent with Windows-based desktop platforms, a Windows CE process immediately terminates if a related secondary thread generates an unhandled exception.

When the Windows CE operating system initializes, it creates a single 4 GB virtual address space. It is divided into 33 slots, each 32 MB. The address space is shared by all processes. When a process initializes, Windows CE selects an open slot for the process in the system’s address space. Slot zero is reserved for the currently running process. In addition to assigning a slot for each process, Windows CE creates a stack and a message queue for each thread in the process. Each stack has an initial size of at least 1 KB. Because the stack size is CPU-dependent; the system allocates 4 KB for each stack on some devices. The maximum number of threads is dependent upon the amount of available memory.

When a process initializes, the operating system stores in the slot assigned to the process all dynamic-link libraries (DLLs), the stack, the heap, the application code, and the data section for each process. DLLs are loaded at the top of the slot, followed by the stack, the heap, and the executable file (.exe). The bottom 64 KB is always left free.

For an overview of processes and threads, see Overview of the Windows CE Operating System.


Creating and Terminating a Process

When you initiate an application from within a running application, the application calls the CreateProcess function to load the new application into memory and to create a new process with at least one new thread.

To create and terminate a process

  1. Call the CreateProcess function to create a process.

    The lpApplicationName parameter must specify the name of the module to execute. Windows CE does not support passing NULL for lpApplicationName.

  2. Call the TerminateProcess function to terminate a process.

    Windows CE processes do not have exit codes and cannot terminate themselves. You cannot use TerminateProcess to terminate a Protected Server Library (PSL) for processes contained therein.

The intent of creating threads in an application is to make use of as much of the CPUs time as possible. To aid in measuring this performance for your application, use the GetThreadTimes function. GetThreadTimes returns the total time that a given process has taken to perform an assigned task.


Scheduling Threads

When the operating system creates a new process, it also creates at least one thread and assigns that thread a priority level. Processes running under Windows CE are not assigned a priority class, so preemption is based solely on the thread’s priority. Threads with a higher priority are scheduled to run first. Threads with the same priority level run in a round-robin fashion, with each receiving a slice of execution time. Threads at a lower priority do not run until all threads with a higher priority have finished. All threads are created with a default priority of THREAD_PRIORITY_NORMAL.

To change the priority level of a thread

  • Call the SetThreadPriority!alink(_wcesdk_win32_SetThreadPriority) function passing in one of eight priority level values. The following table shows the values.
Priority
Description
THREAD_PRIORITY_TIME_CRITICAL | Indicates 3 points above normal priority.
THREAD_PRIORITY_HIGHEST | Indicates 2 points above normal priority.
THREAD_PRIORITY_ABOVE_NORMAL | Indicates 1 point above normal priority.
THREAD_PRIORITY_NORMAL | Indicates normal priority.
THREAD_PRIORITY_BELOW_NORMAL | Indicates 1 point below normal priority.
THREAD_PRIORITY_LOWEST | Indicates 2 points below normal priority.
THREAD_PRIORITY_ABOVE_IDLE | Indicates 3 points below normal priority.
THREAD_PRIORITY_IDLE | Indicates 4 points below normal priority.


Synchronizing Processes and Threads

Windows CE supports preemptive multitasking. Multitasking operating systems must ensure that processes and threads are synchronized. Windows CE provides many ways to coordinate multiple threads of execution. For example, you can use wait functions and synchronization objects. You pass a synchronization object as a parameter to a wait function. The wait function does not return until its specified criteria has been met. The type of wait function determines the set of criteria used. When a wait function is called, it checks whether the wait criteria has been met. If the criteria has not been met, the calling thread enters an efficient wait state, consuming very little processor time.


Using Wait Functions

Windows CE supports two types of wait functions, single-object and multiple-objects. The single object function is WaitForSingleObject. The multiple object functions are WaitForMultipleObjects and MsgWaitForMultipleObjects.

The WaitForSingleObject function requires a handle of one synchronization object. This function returns when one of the following events occurs:

  • The specified object is in the signaled state.
  • The time-out interval elapses. You can set the time-out interval to INFINITE to specify that the wait will not time out.

The WaitForMultipleObjects and MsgWaitForMultipleObjects functions enable the calling thread to specify an array containing one or more synchronization object handles. These functions return when one of the following events occurs:

  • The state of any one of the specified objects is set to signaled or the states of all objects have been set to signaled. You control whether one or all of the states will be used in the function call.
  • The time-out interval elapses. You can set the time-out interval to INFINITE to specify that the wait will not time out.

The following code example shows how to use the CreateEvent function to create two event objects. It then uses the two created objects as parameters in the function call to WaitForMultipleObjects. The WaitForMultipleObjects function does not return until one of the objects is set to signaled.

HANDLE hEvents[2];
DWORD i, dwEvent;

for (i = 0; i < 2; i++)
{
   hEvents[i] = CreateEvent(
      NULL,            // No security attributes
      FALSE,           // Auto-reset event object
      FALSE,           // Initial state is nonsignaled
      NULL);           // Unnamed object
   if (hEvents[i] == NULL)
   {
      printf("CreateEvent error: %d\n", GetLastError() );
      ExitProcess(0);
   }
}

dwEvent = WaitForMultipleObjects(
   2,                  // Number of objects in array
   hEvents,            // Array of objects
   FALSE,              // Wait for any
   INFINITE);          // Indefinite wait

switch (dwEvent)
{
   case WAIT_OBJECT_0 + 0:
      break;

   case WAIT_OBJECT_0 + 1:
      break;

   default:
      printf("Wait error: %d\n", GetLastError());
      ExitProcess(0);
}

MsgWaitForMultipleObjects is similar to WaitForMultipleObjects, except that it allows you to specify input event objects in the object handle array. You select the type of input event to wait for in the dwWakeMask parameter. MsgWaitForMultipleObjects does not return if there is unread input of the specified type in the queue. It returns only when new input arrives.

For example, a thread can use MsgWaitForMultipleObjects with its dwWakeMask parameter set to QS_KEY. This blocks its execution until the state of a specified object has been set to signaled and there is keyboard input available in the thread’s input queue. The thread can use the GetMessage or PeekMessage function to retrieve the input.

When waiting for the states of all objects to be set to signaled, the multiple-object functions do not modify the states of the specified objects until the states of all objects have been set to signaled. For example, the state of a mutex object can be signaled, but the calling thread does not get ownership until the states of the other objects specified in the array have also been set to signaled. In the meantime, some other thread may get ownership of the mutex object, thereby setting its state to nonsignaled.

The following code example shows the use of the MsgWaitForMultipleObjects function in a message loop. The loop continues until a WM_QUIT message appears in the queue. The dwWakeMask parameter is set to QS_ALLINPUT so all messages are checked.

int MessageLoop
   (
      HANDLE* lphObjects,         // Handles that need to be waited on
      int     cObjects            // Number of handles to wait on
   )
{
      while (TRUE)
      {
         // Block-local variable
         DWORD result ;
         MSG msg ;

         while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
         {
            if (msg.message == WM_QUIT)
               return 1;
            DispatchMessage(&msg);
         }

         result = MsgWaitForMultipleObjects(cObjects, lphObjects,
                  FALSE, INFINITE, QS_ALLINPUT);

         if (result == (WAIT_OBJECT_0 + cObjects))
         {
             continue;
         }
         else
         {
             Other Code (result - WAIT_OBJECT_0) ;
         }
   }
}

Be careful when using the wait functions and code that directly or indirectly create windows. If a thread creates any windows, it must process messages. Message broadcasts are sent to all windows in the system. If you have a thread that uses a wait function with no time-out interval, the system will deadlock. Two examples of code that indirectly create windows are DDE and COM CoInitialize. If you have a thread that creates windows, use MsgWaitForMultipleObjects rather than the other wait functions.


Using Synchronization Objects

A synchronization object is an object whose handle can be specified in one of the wait functions. Windows CE uses event, mutex, and critical section objects only for synchronization. Although it uses process and thread objects for synchronization as well, they are available for other uses.


Event Objects

An event object is a synchronization object that allows one thread to notify another that an event has occurred. A thread uses CreateEvent to create an event object. The creating thread specifies the initial state of the object and whether it is a manual-reset or auto-reset event object. The creating thread can also specify a name for the event object. Threads in other processes can open a handle of an existing event object by specifying its name in a call to CreateEvent. For additional information about names for mutex and event objects, see Interprocess Synchronization.

Windows CE uses event objects to tell a thread when to perform its task or to indicate that a particular event has occurred. For example, a thread that writes to a buffer resets the event object to signaled when it has finished writing. By using an event object to notify the thread that its task is finished, the thread can immediately start performing other tasks.

A single thread can specify different event objects in several simultaneous overlapped operations. If this is the case, use one of the multiple-object wait functions to wait for the state of any one of the event objects to be signaled. You can also use event objects in a number of situations to notify a waiting thread of the occurrence of an event. For example, overlapped input/output (I/O) operations on files, named pipes, and communications devices use an event object to signal their completion. For more information about the use of event objects in overlapped I/O operations, see Synchronization and Device I/O.

In the following code example, an application uses event objects to prevent several threads from reading from a shared memory buffer while a master thread is writing to that buffer. The master thread uses the CreateEvent function to create a manual-reset event object. It sets the event object to nonsignaled when it is writing to the buffer and then resets the object to signaled when it has finished writing. The master thread then creates several reader threads and an auto-reset event object for each thread. Each reader thread sets its event object to signaled when it is not reading from the buffer.

#define NUMTHREADS 4

HANDLE hGlobalWriteEvent;

void CreateEventsAndThreads(void)
{
      HANDLE hReadEvents[NUMTHREADS], hThread;
      DWORD i, IDThread;

      hGlobalWriteEvent = CreateEvent
         (
         NULL,                          // No security attributes
         TRUE,                          // Manual-reset event
         TRUE,                          // Initial state is signaled
         "WriteEvent"                   // Object name
         );

      if (hGlobalWriteEvent == NULL)
      {
                                        // Error exit
      }

      for(i = 1; i <= NUMTHREADS; i++)
      {
         hReadEvents[i] = CreateEvent
            (
            NULL,                       // No security attributes
            FALSE,                      // Auto-reset event
            TRUE,                       // Initial state is signaled
            NULL);                      // Object not named

         if (hReadEvents[i] == NULL)
         {
                                        // Error exit
         }

         hThread = CreateThread(NULL, 0,
            (LPTHREAD_START_ROUTINE) ThreadFunction,
            &hReadEvents[i],            // Pass event handle
            0, &IDThread);
         if (hThread == NULL)
         {
                                        // Error exit
         }
      }
}

In the following code example, before the master thread writes to the shared buffer, it uses the ResetEvent function to set the state of hGlobalWriteEvent, an application-defined global variable, to nonsignaled. This blocks the reader threads from starting a read operation. The master thread then uses the WaitForMultipleObjects function to wait for all reader threads to finish any current read operations. When WaitForMultipleObjects returns, the master thread can safely write to the buffer. After it has finished writing, it sets hGlobalWriteEvent and all the reader-thread events to signaled, which enables the reader threads to resume their read operations.

VOID WriteToBuffer(VOID)
{
      DWORD dwWaitResult, i;

      if (! ResetEvent(hGlobalWriteEvent) )
      {
                                        // Error exit
      }

      dwWaitResult = WaitForMultipleObjects
         (
         NUMTHREADS,                    // Number of handles in array
         hReadEvents,                   // Array of read-event handles
         TRUE,                          // Wait until all are signaled
         INFINITE);                     // Indefinite wait

      switch (dwWaitResult)
      {
         case WAIT_OBJECT_0:
                                        // Write to the shared buffer.
               break;

                                        // An error occurred.
         default:
               printf("Wait error: %d\n", GetLastError());
               ExitProcess(0);
      }

      if (! SetEvent(hGlobalWriteEvent) )
      {
                                        // Error exit
      }

      for(i = 1; i <= NUMTHREADS; i++)
         if (! SetEvent(hReadEvents[i]) )
         {
                                        // Error exit
         }
}

In the following code example, before starting a read operation, each reader thread uses WaitForMultipleObjects to wait for the application-defined global variable, hGlobalWriteEvent, and its own read event to be signaled. When WaitForMultipleObjects returns, the reader thread’s auto-reset event has been reset to nonsignaled. This blocks the master thread from writing to the buffer until the reader thread uses the SetEvent function to set the event’s state back to signaled.

VOID ThreadFunction(LPVOID lpParam)
{
      DWORD dwWaitResult, i;
      HANDLE hEvents[2];

      hEvents[0] = (HANDLE) *lpParam;   // The thread's read event
      hEvents[1] = hGlobalWriteEvent;

      dwWaitResult = WaitForMultipleObjects
         (
         2,                             // Number of handles in array
         hEvents,                       // Array of event handles
         TRUE,                          // Wait till all are signaled
         INFINITE);                     // Indefinite wait

      switch (dwWaitResult)
      {

         case WAIT_OBJECT_0:
               break;

                                        // An error occurred.
         default:
            printf("Wait error: %d\n", GetLastError());
            ExitThread(0);
      }

      if (! SetEvent(hEvents[0]) )
      {
                                        // Error exit
      }
}


Mutex Objects

A mutex object is a synchronization object whose state is set to signaled when it is not owned by any thread, and nonsignaled when it is owned. Its name comes from its usefulness in coordinating mutually-exclusive access to a shared resource. Only one thread at a time can own a mutex object. For example, to prevent two threads from writing to shared memory at the same time, each thread waits for ownership of a mutex object before executing the code that accesses the memory. After writing to the shared memory, the thread releases the mutex object.

A thread uses the CreateMutex function to create a mutex object. The creating thread can request immediate ownership of the mutex object and can also specify a name for the mutex object. Threads in other processes can open a handle to an existing mutex object by specifying its name in a call to CreateMutex. For additional information about names for mutex and event objects, see Interprocess Synchronization.

Any thread with a handle of a mutex object can use one of the wait functions to request ownership of the mutex object. If the mutex object is owned by another thread, the wait function blocks the requesting thread until the owning thread releases the mutex object using the ReleaseMutex function. The return value of the wait function indicates whether the function returned for some reason other than the state of the mutex being set to signaled.

Once a thread owns a mutex, it can specify the same mutex in repeated calls to one of the wait functions without blocking its execution. This prevents a thread from deadlocking itself while waiting for a mutex that it already owns. To release its ownership under such circumstances, the thread must call ReleaseMutex once for each time that the mutex satisfied the conditions of a wait function.

If a thread terminates without releasing its ownership of a mutex object, the mutex object is considered to be abandoned. A waiting thread can acquire ownership of an abandoned mutex object, but the wait function’s return value indicates that the mutex object is abandoned. To be safe, assume that an abandoned mutex object indicates that an error has occurred and that any shared resource being protected by the mutex object is in an undefined state. If the thread proceeds as though the mutex object had not been abandoned, the object’s abandoned flag is cleared when the thread releases its ownership. This restores typical behavior, if a handle to the mutex object is subsequently specified in a wait function.

In the following code examples, a process uses the CreateMutex function first to create a named mutex object, and in the second piece of code, to open a handle of an existing mutex object. Additionally, it uses structured exception-handling to ensure that the thread properly releases the mutex object.

HANDLE hMutex;

hMutex = CreateMutex
      (
      NULL,                             // No security attributes
      FALSE,                            // Initially not owned
      "MutexToProtectDatabase");        // Name of mutex

if (hMutex == NULL)
{
                                        // Check for error.
}

When a thread of this process writes to the database, it first requests ownership of the mutex. If it gets ownership, the thread writes to the database and then releases its ownership.

The code example uses the try-finally structured exception-handling syntax to ensure that the thread properly releases the mutex object. To prevent the mutex object from being abandoned inadvertently, the finally block of code is executed no matter how the try block terminates–unless the try block includes a call to the TerminateThread function.

BOOL FunctionToWriteToDatabase(HANDLE hMutex)
{
      DWORD dwWaitResult;

      dwWaitResult = WaitForSingleObject(
         hMutex,        // Handle of mutex
         5000L);        // Five-second time-out interval

      switch (dwWaitResult)
      {
         case WAIT_OBJECT_0:
            try
            {
                        // Write to the database.
            }

            finally
            {
               if (! ReleaseMutex(hMutex))
               {
                        // Deal with error.
               }

            break;
            }

                        // Cannot get mutex ownership due to time-out.
        case WAIT_TIMEOUT:
            return FALSE;

                        // Got ownership of the abandoned mutex object.
        case WAIT_ABANDONED:
            return FALSE;
      }

      return TRUE;
}


Critical Section Objects

A critical section object is a synchronization object that provides synchronization similar to that provided by mutex objects, except that critical section objects can be used only by the threads of a single process. Like a mutex object, a critical section object can be owned by only one thread at a time, which makes it useful for protecting a shared resource from simultaneous access. There is no guarantee about the order in which threads obtain ownership of the critical section; however, Windows CE processes all threads equally.

A process is responsible for allocating the memory used by a critical section. Typically, this is done by declaring a variable of the type CRITICAL_SECTION. Before the threads of the process can use it, you must initialize the critical section by using the InitializeCriticalSection function.

A thread uses EnterCriticalSection to request ownership of a critical section and it uses the LeaveCriticalSection function to release ownership. If the critical section object is currently owned by another thread, EnterCriticalSection waits indefinitely for ownership. In contrast, when a mutex object is used for mutual exclusion, the wait functions accept a specified time-out interval.

Once a thread owns a critical section, it can make additional calls to EnterCriticalSection without blocking its execution. This prevents a thread from deadlocking itself while waiting for a critical section that it already owns. To release its ownership, the thread must call LeaveCriticalSection once for each time that it entered the critical section.

Any thread of the process can use the DeleteCriticalSection function to release the system resources that were allocated when the critical section object was initialized. After this function has been called, the critical section object can no longer be used for synchronization.

When a critical section object is owned, the only other threads affected are those waiting for ownership in a call to EnterCriticalSection. Threads that are not waiting are free to continue running.

The following code example shows how a thread initializes, enters, and leaves a critical section. As with the mutex example described earlier, this example uses the try-finally structured exception-handling syntax to ensure that the thread calls the LeaveCriticalSection function to release its ownership of the critical section object.

CRITICAL_SECTION GlobalCriticalSection;

InitializeCriticalSection(&GlobalCriticalSection);

{
      EnterCriticalSection(&GlobalCriticalSection);
                       // Access the shared resource.
}
finally
{
                           // Release ownership of the critical section.
      LeaveCriticalSection(&GlobalCriticalSection);
}


Interprocess Synchronization

Because multiple processes can have handles to the same event or mutex object, these objects can be used to accomplish interprocess synchronization. The process that creates an object can use the handle returned by the creation function, CreateEvent or CreateMutex. Other processes can open a handle to the object by using its name in another call to the appropriate creation function.

Named objects provide an easy way for processes to share object handles. The name specified by the creating process is limited to the number of characters defined by MAX_PATH. It can include any character except the backslash path-separator character (\). Once a process has created a named event or mutex object, other processes can use the name to call the appropriate function, either CreateEvent or CreateMutex, to open a handle to the object. Name comparison is case-sensitive.

The names of event and mutex objects share the same name space. If you specify a name that is in use by an object of another type when you create an object, the function succeeds, but GetLastError returns ERROR_ALREADY_EXISTS. To avoid this error, use unique names and be sure to check function-return values for duplicate-name errors.

If the name specified in a call to CreateEvent matches the name of an existing event object, the function returns the handle of the existing object. When using this technique for event objects, however, none of the calling processes should request immediate ownership of the event. If multiple processes do request immediate ownership, you may have difficulty predicting which process will get the initial ownership.

The following code examples show the use of object names by creating and opening named objects. The first process uses the CreateMutex function to create the mutex object. Note that the function succeeds even if there is an existing object with the same name.

HANDLE hMutex;
DWORD dwErr;

hMutex = CreateMutex
      (
      NULL,                           // No security descriptor
      FALSE,                          // Mutex not owned
      "NameOfMutexObject");           // Object name

if (hMutex == NULL)
      printf("CreateMutex error: %d\n", GetLastError() );
else
      if ( GetLastError() == ERROR_ALREADY_EXISTS )
         printf("CreateMutex opened existing mutex\n");
      else
         printf("CreateMutex created new mutex\n");


Synchronization and Device I/O

Windows CE supports both synchronous and asynchronous I/O operations on files and serial communications devices. The WriteFile, ReadFile, and WaitCommEvent functions can be performed either synchronously or asynchronously.

When a function is executed synchronously, it does not return until the operation has been completed. This means that the execution of the calling thread can be blocked for an indefinite period while it waits for a time-consuming operation to finish. Functions called for overlapped operation can return immediately, even though the operation has not been completed. This enables a time-consuming I/O operation to be executed in the background while the calling thread is free to perform other tasks. For example, a single thread can perform simultaneous I/O operations on different handles, or even simultaneous read-and-write operations on the same handle.

Windows CE does not support the overlapped I/O features of Windows NT. The lpOverlapped parameter to ReadFile or WriteFile must be NULL. Therefore, Windows CE cannot signal the event passed in when the I/O operation is completed. However, Windows CE does support simultaneous synchronous or asynchronous calls to ReadFile or WriteFile made by separate threads that are overlapped in time; this is not supported in Windows NT.


Synchronizing Access to a Shared Variable

The functions InterlockedDecrement, InterlockedExchange, and InterlockedIncrement provide a simple mechanism for synchronizing access to a variable that is shared by multiple threads. The threads of different processes can use this mechanism, if the variable is in shared memory.

The InterlockedIncrement and InterlockedDecrement functions combine the operations of incrementing or decrementing the shared variable and checking the resulting value. This atomic operation is useful in a multitasking operating system, in which the system can interrupt one thread’s execution to grant a slice of processor time to another thread. Without such synchronization, one thread could increment a variable, but be interrupted by the system before it can check the resulting value of the variable. A second thread could then increment the same variable. In this scenario, when the first thread receives its next time slice, it checks the value of the variable, which has now been incremented not once, but twice. The interlocked variable-access functions of Windows CE protect against this kind of error. The InterlockedExchange function atomically exchanges the values of the specified variables.


Accessing Persistent Storage

The persistent storage that Windows CE uses to support applications is called the object store. This part of memory is not used for the operating system (OS), but rather for registry entries, a file system, and Windows CE databases. Windows CE also supports storage on mounted file systems, such as PC Card storage devices. Windows CE also supports using DLLs to add your own installable file system drivers.

To create, access, or store a data object, you need to use the appropriate application programming interface (API) for the type of data object with which you are working.

Windows CE persistent storage consists of the following APIs:

  • Object identifier APIs, which allow manipulation of databases in the object store and mounted file systems.
  • File system APIs, which allow manipulation of files and directories in the object store and mounted file systems.
  • Registry APIs, which allow registry manipulation in the object store and external locations.


Object Identifiers

Each object—whether a directory, file, database, database on a mounted file system, or database record—is associated with a unique Windows CE object identifier (CEOID). Each CEOID is unique within its own mounted database volume, but not across multiple database volumes. The system generates a CEOID for each object when it is created. CEOIDs are commonly used to access databases and their records. The following table shows where to obtain the system-generated CEOIDs for the various file system object types.

Object type
Where to obtain the CEOID
Directory or file The dwOID member of WIN32_FIND_DATAW returned by FindFirstFile or FindNextFile

The dwOID member of BY_HANDLE_FILE_INFORMATION returned by GetFileInformationByHandle
Database For any Windows CE version later than 2.0, the return value of CeCreateDatabaseEx or CeFindNextDatabaseEx.

For any Windows CE version earlier than 2.0, the return value of CeCreateDatabase or CeFindNextDatabase
Database record | The return value of CeSeekDatabase, CeReadRecordPropsEx, or CeWriteRecordProps
Mounted database volume | CeMountDBVolume and CeEnumDBVolume return the Windows CE Globally Unique Identifier (CEGUID) of the mounted database volume. This can then be used in conjunction with a CEOID to uniquely reference a database or record when more than one database volume is mounted.

Another use of a CEOID is to obtain information about an object.

To obtain information about a CEOID

  1. Call the CeOidGetInfoEx function.
  2. Pass the CEOID in the CeOidGetInfoEx function’s CEOID parameter.

    The CeOidGetInfoEx function returns object data in a CEOIDINFO structure. This structure’s wObjType member contains a flag indicating the object type. The data structure that is returned depends on the object type. For example, if the object is a database record, the CEOIDINFO structure’s wObjType member contains the flag OBJTYPE_RECORD, indicating that the data for the object consists of a CERECORDINFO structure. If there is no valid object for the CEOID, the return value of CeOidGetInfoEx is FALSE.

The following code example shows how to retrieve information about an object in the object store or a mounted database volume.

CEOID        CeOid;            // Object identifier
PCEGUID        pceguid;            // GUID of the mounted volume
CEOIDINFO    CeObjectInfo;    // Structure into which to retrieve
                            //    object information
TCHAR        szMsg[200];        // String to display with object information

// Assign to pceguid the GUID of the mounted volume
// on which the object resides.
// ...

// Assign to CeOid the OID of the object about which
// you want to get information.
// ...

if (CeOidGetInfoEx(pceguid, CeOid, &CeObjectInfo))
{
    switch (CeObjectInfo.wObjType)
    {
        case OBJTYPE_FILE:
            wsprintf(szMsg, TEXT("Object is a file: %s"),
                    CeObjectInfo.infFile.szFileName);
            break;

        case OBJTYPE_DIRECTORY:
            wsprintf(szMsg, TEXT("Object is a directory: %s"),
                    CeObjectInfo.infDirectory.szDirName);
            break;

        case OBJTYPE_DATABASE:
            wsprintf(szMsg, TEXT("Object is a database: %s"),
                    CeObjectInfo.infDatabase.szDbaseName);
            break;

        case OBJTYPE_RECORD:
            wsprintf(szMsg, TEXT("Object is a record"));
            break;

        case OBJTYPE_INVALID:
            wsprintf(szMsg, TEXT("The object store does not contain a \
                    \valid object that has this object identifier."));
            break;

        default:
            wsprintf(szMsg,TEXT(“Object is of unknown OBJTYPE”));
            break;
    }
}
else
{
    // error handling...

    if (GetLastError () == ERROR_INVALID_HANDLE)
        wsprintf(szMsg, TEXT("Invalid Object ID"));
    else
        wsprintf(szMsg, TEXT("Unknown error"));
}


Working with the File System

The Windows CE file system supports files stored in RAM and ROM. The file system functions available to Windows CE-based applications are those supported by the Windows CE API and the standard runtime library. This section highlights specific points concerning the Windows CE file system functions.

The following table shows some of the file-related tasks that you can perform and their associated function calls.

Task
Function
Copy an existing file to a new file | CopyFile
Create a new directory | CreateDirectory
Open an existing file or create a new file | CreateFile
Write a file in the equivalent of append mode | CreateFile
Overwrite the contents of a file | CreateFile with the TRUNCATE_EXISTING flag set
Truncate or extend files | SetEndOfFile
Read data from a file | ReadFile
Write data to a file | WriteFile

Note Windows CE does not support simultaneous read/write operations. ReadFile does not support asynchronous read operations nor does it support read operations through a socket. WriteFile does not support asynchronous write operations. Files cannot be created with the overlapped attribute set.

A ROM file is a file stored on a device as read-only memory. A device manufacturer can use ROM files to store system data and programs. The GetFileAttributes function has additional return values for files stored in ROM. The FILE_ATTRIBUTE_INROM value identifies the file as located in ROM. The FILE_ATTRIBUTE_ROMMODULE value indicates that the file is designed to be executed in place, without first being copied to RAM. The CreateFile function cannot be used to access executable ROM modules. Use the LoadLibrary and CreateProcess functions instead.


File I/O

This section describes the Windows CE file I/O operations and the data returned by the Windows CE file system API.


File System API

The Windows CE file system API enables applications to create, open, modify, and delete files and directories. It also enables applications to retrieve data from files and retrieve system information about files. The following table shows the functions in the file system API that Windows CE supports.

CopyFile | GetFileInformationByHandle
CreateDirectory | GetFileSize
CreateFile | GetTempPath
DeleteFile | MoveFile
DeviceIoControl | ReadFile
FindClose | RemoveDirectory
FindFirstFile | SetEndOfFile
FindNextFile | SetFileAttribute
FlushFileBuffers | SetFilePointer
GetDiskFreeSpaceEx | WriteFile
GetFileAttributes |

The file system API functions that create, open, and delete files and directories take a string identifying the path and file name as a parameter.

The file system API provides functions used to obtain information about a file or file system. The GetFileInformationByHandle function retrieves information about a file and stores it in a structure of the type BY_HANDLE_FILE_INFORMATION. This information includes creation time, file size, and file attributes, such as whether the file is hidden or is a directory. For more information about file attributes, see Microsoft Visual C++ for Windows CE Programmer’s Reference, Microsoft Press, 1998.

An application can use the GetFileAttributes function to retrieve the file attributes. The CreateFile and SetFileAttributes functions can set most file attributes. However, some attributes can only be set by the OS or by the device manufacturer. The GetFileSize function returns the size of a file. The GetDiskFreeSpaceEx function retrieves organizational information about a file system partition, including the number of bytes per sector, the number of sectors per cluster, the number of free clusters, and the total number of clusters.


Creating and Opening Files

You can use the CreateFile function to create a new file or to open an existing file.

When an application uses CreateFile, it must specify whether it will read from the file, write to the file, or both. The application must also specify what action to take if the file already exists. For example, an application can specify that CreateFile is always used to create the file. The function then creates the file if it does not exist or else overwrites the file if it does exist.

CreateFile also enables an application to specify whether it wants to share the file for reading or writing. When an application opens a file and does not specify that the file may be shared, the file cannot be opened again until the application that originally opened the file closes it. This restriction also applies to the original application that opened the file.

The OS assigns a unique identifier, called a file handle, to each file opened or created. An application can use the file handle in functions that read from, write to, or describe the file. The file handle remains valid until the file is closed. For more information about processes, see Working with Processes and Threads.

To create or open a file with the CreateFile function

  1. Specify the file name.
  2. Specify preferred access information.
  3. Specify the share mode.
  4. Specify any creation instructions.
  5. Specify any attributes you want associated with the file.

In the following code example shows how CreateFile opens an existing file for reading.

HANDLE hFile;

hFile = CreateFile(TEXT("MYFILE.TXT"),     // Open MYFILE.TXT
                GENERIC_READ,              // Open for reading
                FILE_SHARE_READ,           // Share for reading
                NULL,                      // No security
                OPEN_EXISTING,             // Existing file only
                FILE_ATTRIBUTE_NORMAL,     // Normal file
                NULL);                     // No template file

if (hFile == INVALID_HANDLE_VALUE)
{
    // Process error ...
    return;
}

In the preceding code example, CreateFile succeeds only if a file named MYFILE.TXT already exists in the root directory. Check the return value of CreateFile before attempting to use the handle to access the file. If an error occurs, use the GetLastError function to get extended error information and respond accordingly.


Reading and Writing Files

Every open file has a file pointer that specifies the next byte to read or the location to receive the next byte written. When a file is opened for the first time, the system places the file pointer at the beginning of the file. As each byte is read or written, the system advances the file pointer. An application can also move the file pointer by using the SetFilePointer function.

The ReadFile function requires a file handle that is open for reading. ReadFile copies a specified number of bytes—from the current position of the file pointer up to the end of the file—to a specified buffer. The data is read exactly as it exists in the file. No data formatting or parsing is performed.

The WriteFile function requires a file handle that is open for writing. WriteFile copies a specified number of bytes—from the start of the buffer to its end— to a specified file at the location currently pointed to by the file pointer. The function returns the actual number of bytes written in a variable specified by its fourth parameter. The data is written exactly as it exists in the buffer. No data formatting is performed.

When the file pointer reaches the end of a file and the application attempts to read from the file, no error occurs, but no bytes are read. Therefore, reading zero bytes without an error means that the application has reached the end of the file. Writing zero bytes does nothing.

The SetFilePointer function moves the file pointer a specified number of bytes, relative either to the beginning or end of the file or to the file pointer’s current position. If a positive number of bytes is specified, SetFilePointer moves the file pointer toward the end of the file. If a negative number of bytes is specified, SetFilePointer moves the file pointer toward the beginning of the file.

Note Accessing the data buffer while a read or write operation is using the buffer may lead to data corruption in that buffer. Applications must not read from, write to, reallocate, or free the data buffer that a read or write operation is using until the operation completes. When an application uses multiple threads, semaphores or critical sections should be used to ensure that one thread does not access the data buffer while another thread is performing a read /write operation.

The following code example shows how to append one file to the end of another file. The application uses CreateFile to open two files: One.txt for reading and Two.txt for writing. Then, the application uses ReadFile and WriteFile to append the contents of One.txt to the end of Two.txt by reading and writing 4KB blocks.

HANDLE hFile, hAppend;
DWORD  dwBytesRead, dwBytesWritten, dwPos;
char   buff[4096];
TCHAR szMsg[1000];

// Open the existing file.

hFile = CreateFile(
            TEXT("ONE.TXT"),              // Open ONE.TXT
            GENERIC_READ,                 // Open for reading
            0,                            // Do not share
            NULL,                         // No security
            OPEN_EXISTING,                // Existing file only
            FILE_ATTRIBUTE_NORMAL,        // Normal file
            NULL);                        // No template file

if (hFile == INVALID_HANDLE_VALUE)
{
    wsprintf(szMsg, TEXT("Could not open ONE.TXT"));
    return;
}

// Open the existing file, or if the file does not exist,
// create a new file.

hAppend = CreateFile(
            TEXT("TWO.TXT"),              // Open TWO.TXT
            GENERIC_WRITE,                // Open for writing
            0,                            // Do not share
            NULL,                         // No security
            OPEN_ALWAYS,                  // Open or create
            FILE_ATTRIBUTE_NORMAL,        // Normal file
            NULL);                        // No template file

if (hAppend == INVALID_HANDLE_VALUE)
{
    wsprintf(szMsg, TEXT("Could not open TWO.TXT"));
    CloseHandle(hFile);                      // Close the first file.
    return;
}

// Append the first file to the end of the second file.

do
{
    if (ReadFile(hFile, buff, 4096, &dwBytesRead, NULL))
    {
        dwPos = SetFilePointer(hAppend, 0, NULL, FILE_END);
        WriteFile(hAppend, buff, dwBytesRead,
                    &dwBytesWritten, NULL);
    }
} while (dwBytesRead == 4096);

// Close both files.

CloseHandle(hFile);
CloseHandle(hAppend);

return;


Searching for Files and Changing File Attributes

An application can use FindFirstFile, FindNextFile, and FindClose to search for file names that match a specified pattern. The pattern must be a valid file name and may include wildcard characters.

FindFirstFile creates a handle that FindNextFile uses to search for other files with the same pattern. Both functions return information about the found file. This information includes the file name, alternate file name, size, CEOID, attributes, creation time, last access time, and last write time. FindClose destroys the handle created by FindFirstFile.

The following code example shows how to copy all text files to a new directory of read-only files named Textro. If necessary, files in the new directory are changed to read-only.

The application uses FindFirstFile and FindNextFile to search the root directory for all .txt files. Each .txt file is copied to Textro. If the file is not read-only, the application changes directories to Textro and uses SetFileAttributes to convert the copied file to read-only.

After all .txt files have been copied, the application uses FindClose to close the search handle.

WIN32_FIND_DATA FileData;    // Data structure describes the file found
HANDLE hSearch;                // Search handle returned by FindFirstFile
TCHAR szMsg[100];            // String to store the error message
TCHAR szNewPath[MAX_PATH];    // Name and path of the file copied
TCHAR szDirPath[] = TEXT("TEXTRO");

BOOL bFinished = FALSE;

// Create a new directory.

if (!CreateDirectory(szDirPath, NULL))
{
    wsprintf(szMsg, TEXT("Unable to create new directory."));
    return;
}

// Start searching for .txt files in the root directory.

hSearch = FindFirstFile(TEXT("*.txt"), &FileData);
if (hSearch == INVALID_HANDLE_VALUE)
{
    wsprintf(szMsg, TEXT("No .TXT files found."));
    return;
}

// Copy each .txt file to the new directory and change it to read-only,
// if it is not already read-only.

while (!bFinished)
{
    lstrcpy(szNewPath, szDirPath);
    lstrcat(szNewPath, TEXT("\\"));
    lstrcat(szNewPath, FileData.cFileName);
    if (CopyFile(FileData.cFileName, szNewPath, FALSE))
    {
        if (!(FileData.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
        {
            SetFileAttributes(szNewPath,
                FileData.dwFileAttributes | FILE_ATTRIBUTE_READONLY);
        }
    }
    else
    {
        wsprintf(szMsg, TEXT("Unable to copy file."));
    }

    if (!FindNextFile(hSearch, &FileData))
    {
        bFinished = TRUE;

        if (GetLastError() == ERROR_NO_MORE_FILES)
        {
            wsprintf(szMsg, TEXT("Found all of the files."));
        }
        else
        {
            wsprintf(szMsg, TEXT("Unable to find next file."));
        }
    }
}

// Close the search handle.

if (!FindClose(hSearch))
{
    wsprintf(szMsg, TEXT("Unable to close search handle."));
}


Closing and Deleting Files

To use OS resources efficiently, an application should use CloseHandle to close files that it no longer needs. If a file is open when an application terminates, the system closes the file automatically.

To delete a file, call DeleteFile with the name of the file passed as its parameter.

A file must be closed before it can be deleted. The following code example shows how to close and delete Myfile.txt.

CloseHandle(hFile);
DeleteFile(TEXT("MYFILE.TXT"));


Using StorageCards

The Windows CE OS supports several types of PC Card storage devices: ATA flash cards, SRAM cards, and linear flash cards. You can use a PC Card storage device to store Windows CE files and databases. You can transfer files from the object store to the storage card. This process frees memory on your device.

Windows CE detects a storage card when it is inserted or if it is present when the user turns on the computer. Windows CE reserves the folder name Storage Card as the default folder name for a storage card when one is inserted into the device. To create, copy, or delete files on the storage card, open the Storage Card folder and then create, copy, or delete files as you would in the rest of the file system.

Note If multiple storage cards are inserted, or if a storage card has multiple partitions, Windows CE creates multiple directories, one for each partition on each card. The second directory is Storage Card2, the third is Storage Card3, and so on. Windows CE does not reserve these names. If a file or directory with the same name exists in the root directory, Windows CE assigns the partition the next available name.

This section outlines the method an application uses for accessing storage card files. For information on more complex file system support on a mounted volume, see Developing an Installable File System.

The following code example tries to open any Storage Card directories that exist and tests them to see if they are located on a storage card.

TCHAR    szMsg[100];            // String to store the error message
HANDLE    hSearch;                // Search handle returned by FindFirstFile
WIN32_FIND_DATA    fd;            // Data structure describes the file found
BOOL        bFinished = FALSE;    // Flag to indicate whether the loop is done

// Be sure "Storage Card" exists.

hSearch = FindFirstFile(TEXT("\\Storage Card*"), &fd);

if (hSearch == INVALID_HANDLE_VALUE)
{
    wsprintf(szMsg, TEXT("No storage card found."));
    return;
}

do {
    // Test whether the file is on a storage card,
    // not a directory in the root directory.
    // It must have both the directory attribute and
    //      the temporary attribute.

    if (!(  (fd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)
          &&(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ))
    {
       wsprintf(szMsg, TEXT("%s is not a storage card."), fd.cFileName);
    }
    else {
        wsprintf(szMsg, TEXT("%s is a storage card."), fd.cFileName);
    }

    if (!FindNextFile(hSearch, &fd))
    {
        bFinished = TRUE;
        if (GetLastError() != ERROR_NO_MORE_FILES)
        {
            wsprintf(szMsg, TEXT("Error trying to find files matching\
                                \ \"Storage Card*\".")) ;
        }
    }
} while (!bFinished) ;

FindClose (hSearch);        // Close the search handle.
return ;


Working with the Registry

The registry is a hierarchical database in which Windows CE stores information necessary to configure the OS. The registry information is always stored in RAM. It contains information on user profiles, applications, hardware, ports in use, and so on. The registry replaces most of the text-based initialization (.ini) files used in MS-DOS and Windows 3.x configuration files, such as AUTOEXEC.BAT and CONFIG.SYS. An example of a registry key used for synchronization is HKEY_LOCAL_MACHINE\Windows CE Services\Synchronization\Objects.

Windows CE supports a subset of the Win32 registry functions. The differences between the Windows CE functions and their Win32 counterparts are minimal.

Windows CE implements the registry as a RAM-based heap file. Consequently, if power is lost to the device’s RAM, registry information is lost and Windows CE must reload the initial registry from ROM.


Developing an Installable File System

Windows CE provides a method for you to create your own specialized file system. For example, you can use installable file systems to take advantage of special features on a new type of storage device or to restrict what can be done with the files on a standard PC Card storage device. This section discusses how to develop a full-featured installable file system driver (FSD) by using the FSD Manager in Windows CE.


FSD Manager

The FSD Manager (FSDMGR), which is contained in Fsdmgr.dll, manages all OS interaction with installable FSDs. It creates file handles, registers volumes, installs necessary APIs, and maps application calls to the installed APIs. FSDMGR invokes these API functions when an application accesses folders and files on mounted volumes using the file system APIs.

The name of the DLL and the names of the functions it exports start with the name of the associated installable file system. For example, if the name of the file system is MyFSD, then its DLL is Myfsd.dll. Exported functions are prefaced with MyFSD_*.

Note You can name your FSD anything you want. This section uses a hypothetical FSD named MyFSD. You can use this name or choose another.

Applications accessing an installable file system use file system API functions. For example, when an application creates a directory on a device, it calls CreateDirectory. FSDMGR then invokes the function supplied in the FSD for the device.

As a basis for developing an FSD, Windows CE provides the following functions, which are called the FSDMGR services.These functions isolate the FSD from both the OS and the device drivers.

FSDMGR_RegisterVolume | FSDMGR_WriteDisk
FSDMGR_GetDiskInfo | FSDMGR_CreateFileHandle
FSDMGR_GetVolumeName | FSDMGR_ReadDiskEx
FSDMGR_ReadDisk | FSDMGR_CreateSearchHandle
FSDMGR_DeregisterVolume | FSDMGR_WriteDiskEx

In addition to providing services to an FSD for accessing a disk and for registering and deregistering volumes, Fsdmgr.dll also provides entry points to create the handles to be passed back to an application’s CreateFile and FindFirstFile requests. These services are prototyped in Fsdmgr.h.


File System Drivers

An FSD is a DLL that exports file system entry points that map to standard Windows CE file system APIs, such as CreateFile and CreateDirectory. When an application calls a file system API function, and the function is referencing a file on a volume registered by the FSD, FSDMGR maps the call to the FSD.

Windows CE provides a template for functions that you need to develop for a file system. Typically, an FSD exports a full range of functions, but you also have the option of not exporting an entry point for a particular file system API function if you do not want that function to be available. For example, to prevent applications from creating or destroying directories inside volumes belonging to your FSD, do not include CreateDirectory and RemoveDirectory in your FSD. FSDMGR automatically supplies stub functions for any functions that you choose not to supply for your file system. These stub functions return ERROR_NOT_SUPPORTED. As a result, if an application tries to create or remove a directory on a volume that belongs to your FSD, the call fails.

In addition to exporting entry points for the file system API functions, an FSD must also export entry points for FSD_MountDisk and FSD_UnmountDisk. Device Manager calls FSD_MountDisk when a device for which that FSD has been registered is inserted or removed. The FSD_MountDisk and FSD_UnmountDisk entry points are passed double-words that uniquely identify the disk being mounted. These double words are in turn passed back to FSDMGR services that query, read, and write to that disk.


Loading FSDs

FSDs are loaded automatically when a storage device that is registered for an FSD is inserted into a Windows CE device.

When a PC card, such as a storage card, is inserted in a device, the card’s device driver reads the PC Card driver registry entry, which defines the name of the FSD to use for the card. The PC Card driver calls LoadFSD, passing the installable FSD name. Device Manager loads the installable FSD that is passed in with the LoadFSD call. Device Manager checks in MyFSD for the FSD_MountDisk entry point, which is the generic exported entry point for MyFSD_MountDisk. If it is found, Device Manager loads Fsdmgr.dll. Device Manager calls Fsdmgr.dll, passing information about MyFSD.

FSDMGR then calls MyFSD_MountDisk, which is exported from MyFSD.dll. MyFSD_MountDisk locates all volumes on the device and registers them with FSDMGR by calling FSDMGR_RegisterVolume. FSDMGR then displays them as directories in the built-in file system and makes them accessible to applications by means of the file system APIs. Similarly, when the FSD’s MyFSD_UnmountDisk entry point is called, it deregisters the volumes on the device. The following illustration shows the call sequence for loading an installable FSD.

Loading an FSD


Creating an FSD named MyFSD

To create an FSD for a hypothetical file system named MyFSD, you must create a DLL named MyFSD.dll. This DLL contains the data type definitions, header files, and functions for MyFSD.

You can define the PVOLUME, PFILE, and PSEARCH data types in MyFSD.dll. These types are used to declare variables and function parameters that hold information about the mounted volume, the file, and the search handle, respectively. These data types are used throughout the FSD functions. By default, PVOLUME, PFILE, and PSEARCH are defined as double-words. You can redefine PVOLUME, PFILE, and PSEARCH to contain any information that you need. For example, you can redefine them as pointers to customized structures. You must redefine these types prior to including Fsdmgr.h or the default definitions will be used.

The code that is compiled into MyFSD.dll must include Fsdmgr.h. This header file generates the prototypes for all of the functions you write for your file system. If you want to define the PVOLUME, PFILE, or PSEARCH data types, do so before including Fsdmgr.h. You must also define a constant, FSDAPI, which is the name of your file system. The following code example shows how it is included:

// This defines a type which is
// a pointer to a structure containing two DWORDs.
typedef struct {
    DWORD    first_field ;
    DWORD    second_field ;
} TWODWORDS, *PTWODWORDS ;

// In this example, only PSEARCH is redefined.
// PFILE and PVOLUME will default to being defined as DWORDs.
// You can define these types to be different types or the same type,
//     whatever your FSD needs.
#define PSEARCH PTWODWORDS

// Now define the name of the file system.
#define FSDAPI  MyFSD
#include <fsdmgr.h>

// … The rest of your code for implementing MyFSD should follow.

The file system name, the prefix for all functions, and the name of the resulting DLL are all defined by the name that you define for FSDAPI. MyFSD.dll must generate prototypes for all the FSDMGR services that your FSD needs.

MyFSD.dll must export entry points for all the file system functions that you want to export. You must prefix the names of the file system functions with MyFSD_. You must also implement the MyFSD_MountDisk and MyFSD_UnmountDisk functions in MyFSD.dll. These functions are exported using the generic names FSD_MountDisk and FSD_UnmountDisk. FSDMGR uses these functions to identify MyFSD.dll as a legitimate FSD. The following example of a .def file shows the .def file for MyFSD.dll:

LIBRARY     MyFSD
    DESCRIPTION    'My File System for Windows CE'

EXPORTS
    MyFSD_MountDisk
    MyFSD_UnmountDisk
    FSD_MountDisk=MyFSD_MountDisk
    FSD_UnmountDisk=MyFSD_UnmountDisk
    MyFSD_CreateDirectoryW
    MyFSD_RemoveDirectoryW
    MyFSD_GetFileAttributesW
    MyFSD_SetFileAttributesW
    MyFSD_DeleteFileW
    MyFSD_MoveFileW
    MyFSD_DeleteAndRenameFileW
    MyFSD_GetDiskFreeSpaceW
    MyFSD_Notify
    MyFSD_RegisterFileSystemFunction
    MyFSD_FindFirstFileW
    MyFSD_FindNextFileW
    MyFSD_FindClose
    MyFSD_CreateFileW
    MyFSD_ReadFile
    MyFSD_ReadFileWithSeek
    MyFSD_WriteFile
    MyFSD_WriteFileWithSeek
    MyFSD_SetFilePointer
    MyFSD_GetFileSize
    MyFSD_GetFileInformationByHandle
    MyFSD_FlushFileBuffers
    MyFSD_GetFileTime
    MyFSD_SetFileTime
    MyFSD_SetEndOfFile
    MyFSD_DeviceIoControl
    MyFSD_CloseFile
    MyFSD_CloseVolume

The hypothetical file system MyFSD implements the full set of functions for working with a file system. You must supply the functions necessary for your file system. However, as mentioned previously, you have the option of not providing a function in your FSD. For any missing functions, FSDMGR automatically provides stub functions that return ERROR_NOT_SUPPORTED. The complete list of functions you can include in your DLL is given in Microsoft Visual C++ for Windows CE Programmer’s Reference, Software Development Kit: Functions, Microsoft Press, 1998. Generally, these functions are similar to the corresponding Windows CE functions with the addition of a volume identifier parameter.

Note The Reference lists these functions under names beginning with MyFSD_*.


Working with Windows CE Databases

A Windows CE database is a general-purpose, flexible, and structured collection of data. A Windows CE database consists of records, where each record consists of one or more properties. A property refers to a data item that consists of a property identifier, a data type identifier, and the data value. For example, an application could use a database of address records, where the properties of each record include a name, street address, city, state, zip code, and telephone number. The following table shows the database properties supported in Windows CE:

Database property
Description
CEVT_BLOB | A CEBLOB structure
CEVT_FILETIME | A FILETIME structure
CEVT_I2 | A 16-bit signed integer
CEVT_I4 | A 32-bit signed integer
CEVT_LPWSTR | A null-terminated string
CEVT_UI2 | A 16-bit unsigned integer
CEVT_UI4 | A 32-bit unsigned integer
CEVT_BOOL | A 16-bit unsigned integer introduced in Windows CE 2.1.
CEVT_R8 | A 64-bit signed double value introduced in Windows CE 2.1.

Devices that run Windows CE usually ship with several installed databases and allow users and applications to create additional databases. For example, a Handheld PC (H/PC) comes with calendar and task list applications that have databases for user data.

Windows CE databases allow only one level of hierarchy. Records cannot contain other records and they cannot be shared by databases. Each record is unique, has a unique CEOID, and is present in only one database. The maximum size of a record in bytes is given by CEDB_MAXRECORDSIZE. The maximum size of a property is given by CEDB_MAXPROPDATASIZE. Both of these constants are defined in Rapi.h and Windbase.h.

Opening and closing a Windows CE database does not imply that any transactions have occurred. The database is not updated at closing, but rather is updated after each individual call.

It is not possible to lock a Windows CE database to restrict access. Thus, several applications can have open handles to the same database concurrently. However, Windows CE supports several notification messages that tell an application when another application creates, modifies, or deletes database records. The messages are sent to the specified window when you call CeOpenDatabaseEx and supply a non-null window handle.

The following table shows the notification messages that Windows CE supports.

Notification nessage
Description
DB_CEOID_CHANGED | Another thread modified an object in the object store. The message supplies the CEOID.
DB_CEOID_CREATED | Another thread created an object in the object store. The message supplies the CEOID.
DB_CEOID_RECORD_DELETED | Another thread deleted a record. The message supplies the record’s CEOID.

Each database includes information about the database as a whole, such as its name, an optional database type identifier that you can use to group similar databases, and up to four sort keys that describe how the records in the database will be sorted.


Mounting, Unmounting, and Enumerating Database Volumes

You call CeMountDBVol to mount a database volume, such as a permanent storage device. This is required before creating or accessing a database. Likewise, you call CeUnmountDBVol to unmount, or remove, a database volume. However, you do not need to call either of these functions for the object store on the Windows CE device itself, because the object store is always mounted. The object store’s CEGUID can be obtained by using the macro CREATE_SYSTEMGUID.

In a mounted database volume, all write operations are cached. The data is periodically written to permanent storage when the cache fills up. To explicitly force the data to be written to permanent storage, you must call CeFlushDBVol. When you call CeUnmountDBVol, all cached data is flushed to the permanent storage device. You may lose data if there is a power failure or if you reset your Windows CE device before data is flushed from the cache to the permanent storage.

To retrieve the name of a mounted database volume, call CeEnumDBVolumes. This function takes a database CEGUID as a parameter. It returns the name of the volume in its string buffer parameter, and the CEGUID of the next volume in the enumeration sequence in its CEGUID parameter. To begin enumerating with the first database, call CeEnumDBVolumes with an invalid CEGUID created with CREATE_INVALIDGUID. You can enumerate all mounted database volumes by repeatedly calling CeEnumDBVolumes in a loop, passing it the last CEGUID it returned, until an error occurs. To ensure that there was no problem with the enumeration, call GetLastError and check for the ERROR_NO_MORE_ITEMS value.

The following code example shows how to mount the database volume MyDBVol. It opens the database volume if it already exists, or creates the database if it does not exist. It also shows how to enumerate all of the mounted database volumes and flush their information to permanent storage.

CEGUID    *pceguid;                // Pointer to the mounted volume
LPWSTR    lpBuf;                    // Buffer to store the volume name
TCHAR    szError[100];            // String to display with error message
DWORD    dwError,                    // Return value of GetLastError function
        dwNumChars;                // Length of the buffer in characters
BOOLEAN bFinished = FALSE;        // TRUE: done with enumeration
                                // FALSE: not done with enumeration

// Allocate memory for pceguid.
pceguid = (PCEGUID) LocalAlloc (LPTR, sizeof (CEGUID));

// Open the database volume MyDBVol on the storage card if it exists.
// Create it if it does not exist.
if (!CeMountDBVol (
        pceguid,                            // Pointer to a CEGUID
        TEXT("\\Storage Card\\MyDBVol"),    // Name of the database volume
        OPEN_ALWAYS))                    // Create the database volume
                                        //   if it does not exist.
{
    // If the database volume was not opened or created
    wsprintf(szError, TEXT("ERROR: CeMountDBVol failed (%ld)"),
                GetLastError());
}

// Allocate memory for lpBuf.
lpBuf = (LPWSTR) LocalAlloc (LPTR, MAX_PATH);

// Assign MAX_PATH to dwNumChars.
dwNumChars = MAX_PATH;

// Create an invalid pceguid as the input parameter of CeEnumDBVolumes.
CREATE_INVALIDGUID (pceguid);

while (!bFinished)
{
    // Enumerates database volumes
    if (!CeEnumDBVolumes (
                pceguid,            // Points to a mounted volume
                lpBuf,            // Buffer to store the volume name
                dwNumChars))        // Length of buffer in char

    {
        // If error occurs in enumerating

        bFinished = TRUE;        // Finished enumerating

        dwError = GetLastError();

        wsprintf(szError, TEXT("ERROR: CeMountDBVol failed (%ld)"),
                dwError);

        if (dwError == ERROR_INSUFFICIENT_BUFFER)
        {
            // To avoid having to restart the enumeration,
            // free lpBuf and re-allocate a bigger buffer.

            bFinished = TRUE;    // continue with enumerating
            LocalFree (lpBuf);    // free lpBuf

            // Reallocate a bigger buffer.
            // ...
        }
    }
    else                            // succeeded in enumerating
    {
        // Flush the database volume's information to permanent storage.

        if (!CeFlushDBVol (pceguid))
        {
            wsprintf(szError, TEXT("ERROR: CeFlushDBVol failed."));
        }
    }
}

The following code example shows how to unmount database volumes by calling CeUnmountDBVol.

if (!CeUnmountDBVol (pceguid)
{
    wsprintf(szMsg, TEXT("Failed unmounting the database."));
}


The Database GUID Macros

You use a Windows CE GUID (CEGUID) to identify the mounted database volumes. Four macros are used to create or check CEGUIDs.

Use the CREATE_SYSTEMGUID macro to generate a CEGUID that identifies the object store on the Windows CE device. When you need to check if the CEGUID returned from a function call is the object store, use the CHECK_SYSTEMGUID macro.

Some database functions can be called with an invalid CEGUID or they can return one. For example, if you want to use the CeEnumDBVolumes function to enumerate all available databases, you can pass it an invalid CEGUID the first time you call it. This causes the function to begin returning database information from the first database on the system. Use the CREATE_INVALIDGUID macro to create the invalid CEGUID. Likewise, call the CHECK_INVALIDGUID macro to check if a CEGUID is an invalid CEGUID.

If you pass CREATE_SYSTEMGUID to the Ex functions, then access is directed to the object store.


Opening and Creating Databases

Windows CE provides several functions for creating, opening, reading, and deleting databases.

To access records or properties in a database

  1. Call the CeOpenDatabaseEx function.
  2. Pass either the database name or its CEOID to the CeOpenDatabaseEx function.

    The CeOpenDatabaseEx function returns a handle to the database.

  3. Use the handle in subsequent calls to read or modify database properties or records.
  4. Call the CloseHandle function with the database handle when you have completed the previous steps.

Use the CEDB_AUTOINCREMENT flag when calling CeOpenDatabaseEx to increase performance when reading many properties. This flag directs the system to increment the seek pointer automatically every time you access a database property with the CeReadRecordPropsEx function. The seek pointer marks the record that will be read by the next read operation.

When calling CeOpenDatabaseEx, you can specify the identifier of a property to use as the sort order for the database. The system uses the sort order to increment the seek pointer after each subsequent call to CeReadRecordPropsEx, if the CEDB_AUTOINCREMENT flag is specified. The sort order also determines the property that the CeSeekDatabase function uses to traverse the database.

The following code example shows the call to CeOpenDatabaseEx.

For more information about sort order, see Sorting Records. For more information about moving the seek pointer, see Searching for Records.

To create a new database, use CeCreateDatabaseEx.

To create a new database

  1. Call the CeCreateDatabaseEx function.
  2. Pass the CEGUID of the mounted volume on which to create the database in the CEGUID parameter.
  3. Pass the other parameters in the function’s CEDBASEINFO parameter.
  4. Specify the database name in the CEDBASEINFO structure.
  5. Optionally, specify a database type identifier in the CEDBASEINFO structure.
  6. Optionally, specify any sort order specifications in the CEDBASEINFO structure.
  7. Optionally, specify the CEDB_NOCOMPRESS flag in the CEDBASEINFO structure.

    By default, the database will be created as a compressed database. If you do not want it to be compressed, you must set this flag.

Note When you create a database on a PC Card storage device, it is usually preferable to make it noncompressed. This is because the access speed to read and write from a storage card is slower than when reading and writing in RAM, and compressing and decompressing data slows your application further. Create your database as a noncompressed database, unless you expext it to be larger than the storage card can contain if left noncompressed.

If the database grows to fill the storage card, you can call CeSetDatabaseInfoEx with CEDB_VALIDDBFLAGS and the HIWORD set to zero, and the database will compress itself. Compression can take a long time to complete, especially on large databases.

The database type identifier is an optional, application-defined value that allows you to differentiate individual databases. For example, you can have a different type of database for an address book than for a to-do list. The type identifier allows you to group related databases for searching, record management, and enumeration of databases.

To delete a database, the application passes the database’s CEOID to CeDeleteDatabaseEx, which removes the database from its database volume.

The following code example attempts to open a database of addresses by calling the CeOpenDatabaseEx function. If the database does not exist, the code example calls the CeCreateDatabaseEx function to create a new address database with three different sort orders. After creating the database, try again to open the database.

HANDLE OpenDatabase (
            HWND hwndNotify,        // Handle to the window to which
                                // notification messages are posted
            PCEGUID pceguid,        // Pointer to the mounted database
                                //     volume where the database to
                                //     be opened resides
            CEOID CeOid)            // Object identifier of the database
                                //     to be opened
{
    int index;
    DWORD dwError;                // Return value from GetLastError
    HANDLE hDataBase;            // Open handle to the address database
    CENOTIFYREQUEST *pRequest;    // CENOTIFYREQUEST structure
    CEDBASEINFO CEDBInfo;        // Structure containing
                                //     database information
    TCHAR szError[100];            // String to display with error message

    // Allocate memory for pRequest.
    pRequest = (CENOTIFYREQUEST *) LocalAlloc (LPTR,
                                        sizeof (CENOTIFYREQUEST));

    pRequest->dwSize = sizeof (CENOTIFYREQUEST);
    pRequest->hwnd = hwndNotify;
    pRequest->hHeap = NULL;        // Let system allocate memory properly.
    pRequest->dwFlags = 0;        // Notifications are handled as they
                                //     were in Windows CE 1.0.

    hDataBase = CeOpenDatabaseEx(
            pceguid,                // Pointer to the mounted volume
            &CeOid,                // Location for the database identifier
            TEXT("MyDBase"),        // Database name
            0,                    // Sort order; 0 indicates to ignore
            CEDB_AUTOINCREMENT,    // Automatically increase seek pointer
            pRequest);            // Pointer to a CENOTIFYREQUEST
                                //     structure

    if (hDataBase == INVALID_HANDLE_VALUE)
    {
        dwError = GetLastError();

        if (dwError == ERROR_NOT_ENOUGH_MEMORY)
        {
            wsprintf(szError, TEXT("Not enough memory"));
        }
        else
        {
            // Possibility of non-existing database. Create it.

            // Initialize structure CEDBInfo
            memset (&CEDBInfo, 0, sizeof(CEDBInfo));

            // Create the database with the following specified flags.
            // Create the database as uncompressed.
            // You can use CeSetDataBaseInfoEx to compress the Database.
            CEDBInfo.dwFlags =
                    CEDB_VALIDNAME           // szDbaseName is valid
                    | CEDB_VALIDTYPE           // dwDbaseType is valid
                    | CEDB_VALIDDBFLAGS       // HIWORD of dwFlag is valid
                    | CEDB_VALIDSORTSPEC       // rgSortSpecs is valid
                    | CEDB_NOCOMPRESS;       // The database is
                                           //    not compressed.

            // Assign the database name as MyDBase.
            wcscpy (CEDBInfo.szDbaseName, TEXT("MyDBase"));

            // Assign the database type.
            CEDBInfo.dwDbaseType = 0;

            // Set the number of active sort orders to 4.
            // This is the maximum number allowed.
            CEDBInfo.wNumSortOrder = 4;

            // Initialize the array of sort order descriptions.
            for (index = 0; index < CEDBInfo.wNumSortOrder; ++index)
            {
                // Sort in descending order.
                CEDBInfo.rgSortSpecs[index].dwFlags =
                                            CEDB_SORT_DESCENDING;

                // Assign the identifier of the properties to sort by:
                // CEDBInfo.rgSortSpecs[index].propid = ...;
            }

            // Create database "MyDBase".
            CeOid = CeCreateDatabaseEx (pceguid, &CEDBInfo);

            if (CeOid == NULL)
            {
                wsprintf(szError,
                        TEXT("ERROR: CeCreateDatabaseEx failed (%ld)"),
                        GetLastError());
            }
            else    // Succeeded in creating the database. Open it.
            {
                hDataBase = CeOpenDatabaseEx(pceguid, &CeOid,
                                TEXT("MyDBase"), 0, 0, pRequest);
            }
        }
    }

    // return the database handle.
    return hDataBase;
}

The CENOTIFICATION structure must be freed after the notification has been processed. To free the structure, you must call CeFreeNotification.


Getting Information About a Database

Each database in the object store or a mounted database volume contains information about that database as a whole, including its name, type identifier, and sort order specifications. This information is defined in the CEDBASEINFO structure through the CeCreateDatabaseEx and CeSetDatabaseInfoEx functions. As discussed earlier, this information can be accessed using CeOidGetInfoEx.

The database name is a null-terminated string that contains up to 32 characters. The type identifier is a double-word value that can be used for any application-defined purpose, typically to differentiate one type of database from another while enumerating them. The sort order specification determines the order in which the database seek functions examine the records in a database.

The CENOTIFICATION structure contains information about the object store or a mounted database volume. The structure is sent to the application to notify it when a database record is created, modified, or deleted. Use the CeFreeNotification function to free the notification structure.


Enumerating Databases

Enumerating databases is the process of sequentially accessing each database in a group. The group can either include all databases in the object store, only those of a specified type, or mounted volumes. Enumeration can be used when a change needs to be made to all databases of a certain type, or when synchronizing data between a desktop computer and a Windows CE-based device.

To enumerate databases, call the CeFindFirstDatabaseEx and CeFindNextDatabaseEx functions. CeFindFirstDatabaseEx establishes and returns a handle to the enumeration context for the type identifier specified. If the type identifier is zero, the context includes all the databases. Note that CeFindFirstDatabaseEx does not take the CEOID for the first database. Use the handle to the enumeration context to call CeFindNextDatabaseEx repeatedly to obtain the CEOIDs for each database in turn. CeFindNextDatabaseEx returns the value zero when there are no more databases in the enumeration context. To ensure that there was no problem with the enumeration, call GetLastError and check for the ERROR_NO_MORE_ITEMS value.

When the application is finished enumerating databases, it uses CloseHandle to close the handle to the enumeration.

The following code example shows how to enumerate all databases and retrieve information about every database:

DWORD        dwError;            // Return value of GetLastError function
TCHAR        szMsg[100];        // String to display error message
                            //    and database information
HANDLE        hEnumDB;            // Handle to a database enumerator
CEOID        CeOid;            // Object identifier of a database
CEOIDINFO    CeObjectInfo;    // Structure that contains
                            //    database information
PCEGUID        pceguid = NULL;    // Pointer to the mounted volume identifier.
                            //   NULL means all mounted database volumes
                            //   are to be searched.

// Find the first database. Set the database type to 0, so all types of
// databases are enumerated. If pceguid is set to NULL or an invalid
// GUID is created by CREATE_INVALIDGUID, all mounted database volumes
// are searched.

hEnumDB = CeFindFirstDatabaseEx (pceguid, 0);

if (hEnumDB == INVALID_HANDLE_VALUE)
{
    if (GetLastError() == ERROR_OUTOFMEMORY)
        wsprintf(szMsg, TEXT("Out of memory."));
    else
        wsprintf(szMsg, TEXT("Unknown error."));

    return;
}

while ((CeOid = CeFindNextDatabaseEx(hEnumDB, pceguid)) != 0)
{
    // Retrieve database information.
    if (!CeOidGetInfoEx (pceguid, CeOid, &CeObjectInfo))
    {
        // Error handling is omitted.  Uses GetLastError.

        CloseHandle(hEnumDB);    // Close the search handle.

        return;
    }
    else
    {
        wsprintf(szMsg, TEXT("The name of the database is: %s"),
                CeObjectInfo.infDatabase.szDbaseName);
    }
}

// Error handling if CeFindNextDatabaseEx fails
dwError = GetLastError ();

if (dwError == ERROR_KEY_DELETED)
{
    // A database has been deleted during enumeration. You must
    // restart the enumeration process again.
    wsprintf(szMsg, TEXT("a database was deleted during enumeration."));
}
else if (dwError == ERROR_NO_MORE_ITEMS)
{
    wsprintf(szMsg, TEXT("No more item to enumerates."));
}
else
{
    wsprintf(szMsg, TEXT("Unknown error."));
}

// Close the search handle.
CloseHandle(hEnumDB);


Working with Database Records

Several functions allow you to create, modify, and delete database records and their properties.

You can create new records or modify existing records using the CeWriteRecordProps function. The function parameters include the handle to the database and the CEOID of the record to add. If the CEOID is zero, CeWriteRecordProps creates a new record. On a mounted database volume, all write operations are cached. The database subsystem will periodically request a cache flush after a series of operations. If memory is low, the cache is flushed to permanent storage. Unlike the file system that can choose to flush only part of the cache, on a database flush, all blocks are flushed.

To write properties to a record

  1. Create an array of CEPROPVAL structures.
  2. Call the CeWriteRecordProps function.
  3. Pass the handle of the database in the function’s HANDLE parameter.
  4. Pass the CEOID of the record in the function’s CEOID parameter.

    If the passed CEOID is zero, a new record will be created.

  5. Pass the number of properties being set in the function’s property-count parameter.
  6. Pass the address of the array of property values in the function’s CEPROPVAL parameter.

    You must pass at least one property value. You can delete a property by setting the CEDB_PROPDELETE flag for a particular property. CeWriteRecordProps returns the CEOID of the record to indicate success, or zero to indicate failure.

Use the CeDeleteRecord function to delete a record from a database, supplying the CEOID of the record and the handle to the open database that contains the record.

The following code example shows how to create and write four properties: a 16-bit signed integer, a 32-bit signed integer, a null-terminated string, and a binary large object (BLOB) to the new record.

TCHAR szError[50];            // String for displaying an error message
HANDLE hDataBase;            // Database handle returned by CeOpenDatabase
CEOID CeOid;                    // Object identifier of the written record
CEPROPVAL NewRecProps[4];    // New property to be written to a record
LPBYTE lpBlob;                // Pointer to the BLOB data
LPWSTR lpwStr;                // Pointer to a null-terminated Unicode
                            // character string

// Assign values to the properties to be written.

// A 16-bit signed integer
NewRecProps[0].propid = CEVT_I2;
NewRecProps[0].wLenData = 0;
NewRecProps[0].wFlags = 0;
NewRecProps[0].val.iVal = –1;

// A 32-bit signed integer
NewRecProps[1].propid = CEVT_I4;
NewRecProps[1].wLenData = 0;
NewRecProps[1].wFlags = 0;
NewRecProps[1].val.lVal = 0xffffffff;

// A null-terminated string
lpwStr = (LPWSTR) LocalAlloc (LMEM_FIXED, 50 * sizeof(TCHAR));
if ( lpwStr )
    _tcscpy(lpwStr, TEXT("This is a string property"));

NewRecProps[2].propid = CEVT_LPWSTR;
NewRecProps[2].wLenData = 0;
NewRecProps[2].wFlags = 0;
NewRecProps[2].val.lpwstr = lpwStr;

// A CEBLOB structure
lpBlob = (LPBYTE) LocalAlloc(LMEM_FIXED, 50  * sizeof(TCHAR));
if ( lpBlob )
    _tcscpy((LPWSTR) lpBlob, TEXT("This is a blob property"));

NewRecProps[3].propid = CEVT_BLOB;
NewRecProps[3].wLenData = 0;
NewRecProps[3].wFlags = 0;
NewRecProps[3].val.blob.dwCount = 50  * sizeof(TCHAR);
NewRecProps[3].val.blob.lpb  = lpBlob;

// Call function CeOpenDatabaseEx to open the database
// hDataBase = CeOpenDatabaseEx {...}

// Write the properties to a new record.
if (!(CeOid = CeWriteRecordProps(
            hDataBase,        // Handle of the database
            0,                // Write a new record.
            4,                // Four properties
            NewRecProps        // Pointer to the new properties
            )))
{
    wsprintf(szError, TEXT("ERR: CeWriteRecordProps failed (%ld)"),
            GetLastError());
}

// Free local memory lpBlob
if( lpBlob )
    LocalFree(lpBlob);

// Free local memory lpwStr
if( lpwStr )
    LocalFree(lpwStr);

// Close database handle
CloseHandle(hDataBase);


Reading Records and Properties

After your application opens a Windows CE database, the seek pointer is positioned at the first record according to the selected sort order.

The CeReadRecordPropsEx function reads properties from the record where the seek pointer is currently positioned. When calling CeReadRecordPropsEx, indicate the properties to be read by specifying an array of property identifiers. Also specify the buffer into which the function is to write the property information, and specify a value indicating the size of the buffer. If the buffer is too small to hold the property information when you specify the CEDB_ALLOWREALLOC flag, the system reallocates the buffer. By default, the system stores records in compressed format and must decompress records as they are read. For efficiency, your application should read all of the desired properties in a single call rather than in several separate calls.

When the property is read successfully, the property information is copied into the specified buffer as an array of CEPROPVAL structures, and the function returns the record’s CEOID. All of the variable-size data, such as strings and BLOBs, is copied to the end of the buffer. The CEPROPVAL structures contain pointers to this data. If the system cannot find a requested property in the specified record, the CEPROPVAL structure for that property receives the CEDB_PROPNOTFOUND flag.

If you specified the CEDB_AUTOINCREMENT flag when you opened the database, CeReadRecordPropsEx increments the seek pointer.

The following code example shows how to read properties from the database using the auto-increment and reallocation flags.

CEOID    CeOid;                // Object identifier of the record read
PCEGUID    pceguid;                // Pointer to the mounted database volume
WORD        wcPropID;            // Number of properties retrieved
DWORD    dwcbBuffer,            // Count of bytes of the *lpBuffer
        dwError;                // Return value of the GetLastError function
TCHAR    szMsg[100];            // String for displaying the error message
LPBYTE    lpBuffer = NULL;        // Pointer to a buffer that receives record
                            //    property data
HANDLE    hDataBase,            // Handle to a database to be opened
        hHeap;                // Handle to the heap for allocating records

// Assign to pceguid the GUID of the mounted volume
// where the database resides.
// ...

// Create the heap by calling HeapCreate to allocate the database records.
// ...

// Open the database with the current seek position to be automatically
// incremented with each call.

hDataBase = CeOpenDatabaseEx(
            pceguid,                // Pointer to the mounted volume
            &CeOid,                // Location of the database identifier
            TEXT("MyDBase"),        // Database name
            0,                    // Sort order. 0 indicates to ignore.
            CEDB_AUTOINCREMENT,    // Automatically increase seek pointer
            NULL);                // Does not need to receive notification

// Check for errors on the hDataBase handle before continuing.

if (hDataBase == INVALID_HANDLE_VALUE)
{
    // error handling omitted...uses GetLastError()
    return;
}

while ((CeOid = CeReadRecordPropsEx(
            hDataBase,            // Handle of the database
            CEDB_ALLOWREALLOC,    // Use LocalAlloc to allocate the buffer
            &wcPropID,            // Number of properties retrieved
            NULL,                // NULL means retrieve all properties
            &lpBuffer,            // Buffer receives property data
            &dwcbBuffer,            // Count of bytes in *lpBuffer
            hHeap)) !=0)            // Handle to the heap for allocating
                                //    the record
                                // when CEDB_ALLOWREALLOC is specified.
{
    // The record is now available in lpBuffer. Add code here to
    // manipulate the properties in this record
    // ...
}

// Error handling if CeReadRecordPropsEx fails

dwError = GetLastError();

if (dwError == ERROR_NO_MORE_ITEMS)
{
    wsprintf(szMsg, TEXT("Read through all records in the database."));
}
else if (dwError == ERROR_INSUFFICIENT_BUFFER)
{
    wsprintf(szMsg, TEXT("Re-allocation of database records failed."));
}
else
{
    wsprintf(szMsg, TEXT("Other errors."));
}

// Close the database handle.

CloseHandle(hDataBase);


Sorting Records

When you create a new database, you can specify up to four sort orders to associate with the database. You specify a sort order using a SORTORDERSPEC structure that contains the identifier of a property on which the database records are to be sorted. The structure also includes a combination of flags that indicate whether to sort the records in ascending or descending order, whether the sort is case-sensitive, and whether to place records that do not contain the specified property before or after all other records. By default, sorting is done in descending order and is case-sensitive. Records not containing a specified property are placed at the end of all other records.

Note Sorting on binary properties is not allowed.

Because using sort orders on a database increases the amount of time and system resources needed to perform each insertion or deletion, use the minimum number of sort orders for an application. Although it is possible to use CeSetDataBaseInfoEx to reorder the database, this process is expensive in terms of time and system resources. It could take several minutes to reorder a large database. It is more efficient to create the database with the necessary sort orders to begin with.

Typically, each record in a database contains a similar set of properties, and each type of property shares the same property identifier. For example, each record in a Contacts database might contain a name, street address, city, state, zip code, and telephone number. All name properties would have the same property identifier, all street address properties would have the same property identifier, and so on. You can select one of these properties and direct the system to sort the records based on it. The order in which the records are sorted affects the order in which the database-seeking function CeSeekDatabase finds records in the database.

You specify the sort order when you call the CeOpenDatabaseEx function. Only one sort order can be active for each open handle. However, by opening multiple handles to the same database, you can use more than one sort order.

Note Multiple sort orders cannot be specified for a single property.


Searching for Records

Use the CeSeekDatabase function to search for a record in a database. The CeSeekDatabase function always uses the current sort order as specified in the call to CeOpenDatabaseEx. If the CEDB_AUTOINCREMENT flag was specified, each read operation on the database will automatically increment the seek pointer from the current position to the next position.

The CeSeekDatabase function can perform different types of seek operations. When calling the function, you specify a flag that indicates the type of seek operation, and a value whose meaning depends on the specified flag. For example, to find a particular record, you specify the CEDB_SEEK_CEOID flag and the CEOID of the desired record. When CeSeekDatabase finds a record, the seek pointer is positioned at that record. Any subsequent read operation takes place at the location of the seek pointer.

Note A seek operation can only be performed on a sorted property value.

Seek operations are affected by the sort order associated with the open database handle. For example, suppose the Contacts database was opened using a sort operation on the name property. If you specify the CEDB_SEEK_VALUEFIRSTEQUAL flag and a value of “Joe Smith,” the CeSeekDatabase function will search from the beginning of the database looking only at the name property of each record. It will stop when, and if, a matching property is found.

You can use CeSetDatabaseInfoEx to change the sort order that was set when the database was created, but this is expensive in terms of time and resources. The system maintains a set of indexes and other information that it uses to optimize database searches for the specified sort orders. When new sort orders are specified, the system must revise all of this internal information, which can take several minutes for large databases. It is better to set the desired sort order on the database when it is created so that records will be placed in order as they are added to the database. The following code example shows how to call CeSeekDatabase.

CEOID    CeOid;                // Object identifier
CEOID    CeOidSeek;            // Object identifier
DWORD    dwIndex;                // Index of record to seek
TCHAR    szError[100];        // String for displaying error messages
HANDLE    hDataBase;            // Handle to a user-allocated heap

// Assign to CeOidSeek the object identifier of the record
// being searched for.
// ...

// Call CeOpenDatabaseEx to open the database.
// hDataBase = CeOpenDatabaseEx {...}

// Perform the seek. This type of seek operation is very efficient.
if ((CeOid = CeSeekDatabase(
            hDataBase,            // Handle of the database
            CEDB_SEEK_CEOID,        // Finding an object with the
                                //     same identifier.
            CeOidSeek,            // Specifies the record to seek
            &dwIndex                // If successful, move the database
                                //     pointer to point to the record.
            )) == 0)
{
    wsprintf(szError, TEXT("ERROR: CeSeekDatabase failed (%ld)"),
            GetLastError());
}


Synchronizing Data

Microsoft® Windows® CE Services includes Microsoft ActiveSync™, which allows you to synchronize, or coordinate, data stored on a Windows-based desktop computer with data stored on a Windows CE–based device. ActiveSync also enables you to coordinate device data with two or more computers or synchronize information on one computer with several devices.

Note ActiveSync does not support synchronization between two devices, a device and a server, or a device and mounted volumes.

Through serial, infrared, Ethernet LAN, or modem connections, the synchronization process automatically transfers and tracks data changes, including additions, deletions, and other revisions to information. As a result, shared information is always up to date and accurate. ActiveSync is a client/server architecture that consists of a service manager (the server) and a service provider (the client):

  • The service manager, which is built into Windows CE Services, is a synchronization engine that resides on both the desktop and the device. The service manager performs many common synchronization tasks, which include providing connectivity, detecting changes in data, resolving data conflicts, as well as mapping and transferring data objects.
  • The service provider comprises two modules, which typically are dynamic-link libraries (DLLs), that you must implement in your application to perform the synchronization tasks that are specific to your data. One module, called the desktop provider module, resides on the desktop and the other module, called the device provider module, resides on the device.

The service provider interfaces with both the service manager and the application, thereby exposing the application and the application’s data to the service manager. In turn, this interaction enables the service manager to synchronize different types of data from different types of applications. The service provider also facilitates all requests made by the service manager, such as displaying a user interface (UI) or reporting status.

Creating a service provider is a two-part process:

  1. Create the service provider by developing the desktop provider module and the device provider module.
  2. Register the service provider on both the Windows-based computer and the Windows CE–based device.

The service manager implements a single interface, IReplNotify, which is built into ActiveSync and Windows CE Services. Thus, once you register the service provider, the synchronization process is automatic. The service manager, with the help of the service provider, directs and controls all synchronization tasks.

The following illustration shows how the service manager interfaces with the service provider to access data.


Creating an ActiveSync Service Provider

The synchronization process transfers only data that has changed. For this reason, the service provider must track data changes as efficiently as possible to ensure that the least amount of data is transferred during synchronization.

To create an efficient ActiveSync service provider for an application, you first must analyze the data to be synchronized by defining the object, the object type, and the object identifier, as well as the folder and the store to hold the objects. You also must decide on a method to compare objects and a method to indicate that an object has changed. As you do this, keep in mind the following terminology and requirements, which are specific to the synchronization process:

  • An object is a logical unit of data, such as a single appointment. The object definition depends on your application. The definition can be an appointment, an address, or some other item.
  • An object type is a name for a particular group of objects contained in a folder. For example, appointment is an object type naming all appointments in a Microsoft Schedule+ folder.
  • An object identifier is a value that uniquely identifies an object. The object identifier must satisfy the following criteria:
    • It cannot change once it is created.
    • It cannot be reused for any other object.
    • It must be ordered, so that you can determine which object comes first.
    • It must signify changes since the last synchronization.

    For example, globally unique identifiers (GUIDs) satisfy the criteria. The criteria allow the service manager to compare object identifiers to aid in synchronization.

  • The store is a database that holds the data, or objects, to be synchronized. The store must accommodate the objects, but its format can vary. The store can be a flat file, a database, or some other custom format.

Once you have analyzed the data, you are ready to create a service provider by developing the desktop provider module and the device provider module:

  • The desktop provider module handles the bulk of communication with the service manager and implements two Component Object Model (COM) interfaces: IReplStore and IReplObjHandler:
COM interface
Description
IReplStore | Enumerates objects in a store, checks for changes in an object, and displays a UI so that the user can set synchronization options and resolve conflicts.
IReplObjHandler | Serializes data and deletes objects.

  • The device provider module must implement the same IReplObjHandler interface as the desktop provider module, as well as the following functions:
Function
Description
InitObjType | Initializes data and returns a pointer to the IReplObjHandler interface when the device provider module loads and, when the device provider module terminates, frees allocated resources.
ObjectNotify | Calls the service manager when an object in the object store for a device is changed or deleted.
GetObjNotify | Returns the type of the specified object.
ReportStatus | Optional; gets status for synchronization objects.


Developing the Desktop Provider Module

In contrast to the device provider, which has limited functionality, The desktop provider handles the bulk of all communication with the manager. The desktop provider, hereafter referred to simply as the provider, implements two COM interfaces: IReplStore and IReplObjHandler. IReplStore enumerates objects in a store, checks for changes in an object, and displays a user interface so that the user can set synchronization options and resolve conflicts. IReplObjHandler serializes data and deletes objects. For more information on the methods contained in IReplStore and IReplObjHandler, see the Windows CE Programmer Reference.

To implement the desktop provider module

  1. Create one GUID for the store, by using the Microsoft Visual C++® GUID generator tool, Guidgen.exe.
  2. Initialize the store.
  3. Compare store identifiers and handle mismapped store identifiers.
    1. Define object handles to provide access to data objects stored on a device .

    The object handles typically are pointers to data structures and include HREPLITEM, HREPLFLD, and HREPLOBJ. HREPLOBJ is a generic handle to either an item or a folder.

  5. Provide a folder handle for the specified object type and, to access folders, return a pointer to the IReplObjHandler interface.
  6. Enumerate objects for a specified object type.
  8. Detect changes in object types.
  9. Send and receive objects.
  10. Create a UI to set synchronization options for a device and handle conflicts.


Initializing the Store

When a connection event occurs, the service manager calls IReplStore::Initialize to ensure that the data file for the application exists and is open. The service manager:

  1. Initializes the data store and compares store identifiers to determine if the desktop object and the device object are mapped correctly.
  2. Accesses the data store and enumerates each desktop object.
  3. Determines what data has changed and transfers that data to the device.

To accomplish these tasks, the service manager places a series of calls to the desktop provider module, prompting the desktop provider module to respond with the necessary information.

The order in which the service manager calls the desktop provider module is shown in the following flowchart.

In order for the service manager to initialize the desktop provider module, you must store the name of the file that needs to be synchronized in the registry. The registry location for this file name depends on whether you are initializing the desktop provider module for a connected device or a selected device.

A selected device is a disconnected device that a user selects from the list of profiles stored in the Windows CE Services Mobile Device folder. A user selects a profile to change synchronization options. When a user selects a device, the service manager passes the bit flag ISF_SELECTED_DEVICE into IReplStore::Initialize.

To access the profile information for a device:

  • To get the registry key for a connected device profile, call IReplNotify::QueryDevice with QDC_CON_DEVICE_KEY.
  • To get the registry key for a selected device profile, call IReplNotify::QueryDevice with QDC_SEL_DEVICE_KEY.

If a device is remotely connected to the desktop, the service manager passes the bit flag ISF_REMOTE_CONNECTED into IReplStore::Initialize. Whenever this flag is set, the desktop provider module should not display any blocking UI, such as message box or a dialog box, because the user may not be able to respond to the UI. Instead, the desktop provider module should accept all default actions, without prompting the user on the desktop.

If the service provider returns an error in IReplStore::Initialize, the service manager automatically displays an error message. If you want the desktop provider module, rather than the service manager, to display an error message that you create, return RERR_NO_ERR_PROMPT.

The service manager typically calls IReplStore::Initialize immediately after a connection event occurs. However, the service manager can call other methods first, such as when a user changes the synchronization options for a disconnected device.

The following table shows the methods that can be called before IReplStore::Initialize.

IReplStore method
Description
IReplStore::GetStoreInfo | Gets information about a store, which is displayed in the ActiveSync option dialog box.
IReplStore::GetObjTypeUIData | Gets information about an object type, which is displayed in the ActiveSync option dialog box under Status.
IReplStore::GetFolderInfo | Gives the folder handle, HREPLFLD, to the desktop provider module.
IReplStore::ActivateDialog | Allows a user to change options for an ActiveSync service.
IReplStore::BytesToObject | Converts a series of bytes to a HREPLITEM handle or a HREPLFLD handle.
IReplStore::ObjectToBytes | Converts a HREPLITEM handle or a HREPLFLD handle to a series of bytes.
IReplStore::ReportStatus | Informs the desktop provider module about events that are taking place. This method is optional.

The following code example shows how to implement IReplStore::Initialize.

STDMETHODIMP CStore::Initialize
(
    IReplNotify *pNotify,    // Pointer to the IReplNotify interface.
    UINT uFlags              // Either ISF_SELECTED_DEVICE or                                         // ISF_REMOTE_CONNECTED.
)
{
    m_pNotify = pNotify;
    m_uFlags = uFlags;
    // Get the correct registry for the synchronization options.
    HKEY hKey;
    HRESULT hr;
    hr = m_pNotify->QueryDevice( ( uFlags & ISF_SELECTED_DEVICE )?                     QDC_SEL_DEVICE_KEY : QDC_CON_DEVICE_KEY, (LPVOID *)&hKey );

// Read the registry for the type of files to synchronize.

// ...

    // The service provider should suppress all UI that requires
    // user input
    // if ISF_REMOTE_CONNECTED is set in uFlags.

// ...

    return NOERROR;
}

CStore is a COM class whose base is IReplStore. The following code example shows the CStore definition.

class CStore: public IReplStore
{
private:
    LONG                m_cRef;
    LPUNKNOWN        m_pUnkOuter;
public:
    CStore( LPUNKNOWN );
    ~CStore();
    // ******** IUnknown methods **************
    // QueryInterface, AddRef, and Release
    // ******** IReplStore methods **************
    // Store manipulation routines
     // Initialize, GetStoreInfo, ReportStatus, CompareStoreIDs
    // Object-related routines
     // CompareItem, IsItemChanged, IsItemReplicated, UpdateItem
    // Folder-related routines
    // GetFolderInfo, IsFolderChanged
    // Enumeration of folder objects
    // FindFirstItem, FindNextItem, FindItemClose
    // STD management routines
     // ObjectToBytes, BytesToObject, FreeObject, CopyObject,
    // IsValidObject
    // UI-related routines
    // ActivateDialog, GetObjTypeUIData, GetConflictInfo,
    // RemoveDuplicates
private:
    IreplNotify        *m_pNotify;
    CDataHandler    *m_pObjHandler;
    UINT                m_uFlags;
};


Comparing Store Identifiers

When a user connects a device to a desktop:

  1. The service manager calls IReplStore::GetStoreInfo to retrieve the store’s identifier number.

    This number is stored in Repl.dat.

  2. The service manager reads the store identifier for the store to be synchronized and passes this identifier number to IReplStore::CompareStoreIDs.
  3. The desktop provider module determines whether this store identifier matches the one loaded from Repl.dat, which identifies the store that was used in the previous synchronization.

If the identifiers do not match, the service manager reestablishes the mapping between the desktop and device objects by issuing a request to the user either to combine two sets of data or to discard the device data and send all desktop objects to the device.

The combine and discard process is required when Repl.dat is corrupt and cannot be read, as well as when the user:

  • Synchronizes a device with existing data for the first time.
  • Uses an existing device to delete the device profile, and then reconnects the device to create a new partnership.
  • Chooses a desktop store that is different from the one that was used in the last synchronization. The service manager detects the new selection by comparing store identifiers.
  • Restores the device data from a backup file.
  • Does a discard operation on one computer, and then synchronizes the device with a second computer.

If the user chooses to combine the data, all objects on both the device and the desktop are marked as changed. If the user chooses to discard the data, all objects on the device are marked as deleted and all desktop objects are marked as changed. Synchronization occurs immediately after the user makes a selection.

Combining data typically creates duplicate objects in the desktop store. To find each duplicated object in the desktop store and remove them, use IReplStore::RemoveDuplicates. The service manager calls this method after the first successful synchronization occurs. Once duplicated objects are removed, the desktop provider module returns control to the service manager to begin enumerating the store. Through enumeration, the service manager determines which desktop objects are deleted and informs the device to remove the corresponding device objects.

In addition to using IReplStore::GetStoreInfo to provide identification information, you can use this method to perform other tasks, such as changing the size of an object store or the time interval between enumerations. When the service manager calls IReplStore::GetStoreInfo, it passes the structure STOREINFO. Use STOREINFO to change store information:

  • To receive a variable-sized store identifier, the service manager calls IReplStore::GetStoreInfo with STOREINFO::cbMaxStoreId set to 0. This call should prompt the desktop provider module to set the required size for the store identifier in STOREINFO::cbStoreId and return E_OUTOFMEMORY. In response, the service manager allocates the required memory and passes a pointer to the store identifier in STOREINFO::lpbStoreId. The desktop provider module then can use this pointer to save the store identifier.
  • If your desktop provider module does not support real time notification of changes, you need to set STOREINFO::uTimerRes:
    • To enable the service manager to automatically start enumerating the store at a set interval, set STOREINFO::uTimerRes to the non-zero time, in micro-seconds, that you want to use for the interval.
    • To enable the service manager to begin the enumeration process only when the user activates the ActiveSync status window or immediately before synchronization begins, set STOREINFO::uTimerRes to –1.

The following example shows how to implement the IReplStore::CompareStoreIDs and IReplStore::GetStoreInfo methods and the STOREINFO structure.

STDMETHODIMP_(int) CStore::CompareStoreIDs
(
    LPBYTE  lpbID1,     // Points to the first store identifier.
    UINT    cbID1,      // The size of the first store identifier.
    LPBYTE  lpbID2,     // Points to the second store identifier.
    UINT    cbID2       // The size of the second store identifier.
)
{
    // If the size of the first store identifier is smaller than
    // the size of the second store identifier
    if ( cbID1 < cbID2 )
        return -1;
    // If the size of the first store identifier is larger than
    // the size of the second store identifier
    if ( cbID1 > cbID2 )
        return 1;
    return memcmp( lpbID1, lpbID2, cbID1 );
}
STDMETHODIMP CStore::GetStoreInfo
(
    PSTOREINFO pInfo    // Pointers to the STOREINFO structure.
)
{
    if ( pInfo->cbStruct != sizeof( STOREINFO ) )
        return E_INVALIDARG;
    pInfo->uFlags = SCF_SINGLE_THREAD | SCF_SIMULATE_RTS;
    // ProgId of the store. You can change this to match your company
    // and your product.
    lstrcpy( pInfo->szProgId, "MyCompany.WinCE.DeskSamp" );
    // The description of the store. This will be displayed to the user.
    // You can change this to suit your requirements.
    lstrcpy( pInfo->szStoreDesc, "Files" );
    // Let replication scan the store every 5 seconds.
    pInfo->uTimerRes = 5000;
    // Construct something that uniquely identifies the store. In this     // sample, because the difference in the stores are inconsequential,
    // set the size of the store identifier to any value.
    pInfo->cbStoreId = 10;
    // Compare the size of the the store identifier with the maximum
    // size of the store identifier.
    if ( pInfo->cbStoreId > pInfo->cbMaxStoreId )
        return E_OUTOFMEMORY;
    // Check if the pointer to the store identifier is NULL.
    if ( pInfo->lpbStoreId == NULL )
        return E_POINTER;
    memset( pInfo->lpbStoreId, 0, 10 );
    return NOERROR;
}


Accessing Objects

In order for the service manager to access a data object, you must provide an HREPLITEM handle to the object. HREPLITEM is an important data type because each handle uniquely identifies one object. To the service manager, this handle is a 32-bit number created by the desktop provider module. To the desktop provider module, this handle is a pointer to an internal structure or class instance.

Whenever the service manager needs information about the object, it calls methods in IReplStore or IReplObjHandler and passes the HREPLITEM of the object as a parameter. From the object identifier and the time stamp or version number contained in the HREPLITEM, the desktop provider module can determine whether two handles represent the same object, as well as which of the two handles represents a more recent version of the object.

To compare information, IReplStore::CompareItem checks the object identifiers contained in the two handles. The following table shows the possible return values for this method.

Value
Condition
1 | The first object identifier is greater than the second object identifier.
–1 | The first object identifier is less than the second object identifier.
0 | The first object identifier is equal to the second object identifier.

The ordering of the object identifiers allows the service manager to use a binary search on its table of object identifiers.

The following code example shows how to implement IReplStore::CompareItem.

STDMETHODIMP_(int) CStore::CompareItem
(
    HREPLITEM hItem1,    // Points to the handle of first object.
    HREPLITEM hItem2        // Points to the handle of second object.
                        // Both of the handles are guaranteed to be
                        // returned by IReplStore::FindFirstObject or
                        // IReplStore::FindNextObject.
)
{
    CItem   *pItem1 = (CItem *)hItem1;
    CItem   *pItem2 = (CItem *)hItem2;
    if ( pItem1->m_uid == pItem2->m_uid )
        return 0;
    if ( pItem1->m_uid < pItem2->m_uid )
        return -1;
    return 1;
}

CFolder and CItem are COM classes based on CReplObject. The following code example shows the definition of these classes.

#define OT_ITEM     1
#define OT_FOLDER   2
class CReplObject
{
public:
    virtual ~CReplObject() {}
    UINT    m_uType;
};
class CFolder: public CReplObject
{
public:
    CFolder( void ) { m_uType = OT_FOLDER; }
    virtual ~CFolder() {}
};
class CItem: public CReplObject
{
public:
    CItem( void ) { m_uType = OT_ITEM;
                    memset( &m_ftModified, 0, sizeof( m_ftModified )); }
    virtual ~CItem() {}
    UINT        m_uid;
    FILETIME    m_ftModified;
};


Accessing Folders

For the service manager to access a folder, you must implement HREPLFLD, which is a handle that identifies a folder. This handle contains the filter for the object type and other object-specific information.

The service manager calls IReplStore::GetFolderInfo to obtain the folder handle. The service manager also calls IReplStore::IsFolderChanged, IReplStore::CopyObject, IReplStore::IsValidObject, and IReplStore::FreeObject to manipulate folder and item handles.

Because the service manager saves the data that is stored in the HREPLITEM or HREPLFLD handles to Repl.dat, which is a file that the service manager creates and maintains, you must also implement IReplStore::ObjectToBytes and IReplStore::ObjectToBytes.

The following code example shows how to implement IReplStore::GetFolderInfo.

STDMETHODIMP CStore::GetFolderInfo
(
    LPSTR lpszName,            // The name of the object type taken from
                            // the registry.
    HREPLFLD *phFolder,        // Output pointers to the handle of the
                            // new folder.
    IUnknown **ppObjHandler    // Output pointers to the pointer of
                            // the IReplObjHandler interface.
)
{
    // Check if phFolder points to a handle that has NULL value.
    CFolder *pFolder = (CFolder *)*phFolder;
    BOOL fNew = (pFolder == NULL);
    // Create a new handle for the specific folder.
    if ( fNew )
        pFolder = new CFolder;
    // Either set up the new CFolder class (when fNew is TRUE) or
    // reinitialize the class (when fNew is FALSE).

// ...

    *phFolder = (HREPLFLD)pFolder;
    *ppObjHandler = m_pObjHandler;
    return NOERROR;
}

To determine if any object contained in a folder has changed, the service manager calls IReplStore::IsFolderChanged. The service manager also can call the following methods to manipulate folder or item handles:

  • IReplStore::CopyObject to copy the data from one handle to another handle that represents the same object.
  • IReplStore::IsValidObject to ensure that the handle still represents a valid object, not a deleted object.
  • IReplStore::FreeObject to remove a handle from memory, thereby allowing the desktop provider module to free the memory resources that are used by the handle.

The following code examples show how to implement each one of these methods.

STDMETHODIMP_(BOOL) CStore::CopyObject
(
    HREPLOBJ    hObjSrc,    // The handle to the source object.
    HREPLOBJ    hObjDst     // The handle to the destination object.
)
{
    CReplObject *pObjSrc = (CReplObject *)hObjSrc;
    CReplObject *pObjDst = (CReplObject *)hObjDst;
    // Check to see if the source and destination types are the same.
    if ( pObjSrc->m_uType != pObjDst->m_uType )
        return FALSE;
    switch( pObjSrc->m_uType )
    {
    case OT_ITEM:            // If the source object is an item
        ((CItem *)pObjDst)->m_uid = ((CItem *)pObjSrc)->m_uid;
        ((CItem *)pObjDst)->m_ftModified =
                            ((CItem *)pObjSrc)->m_ftModified;
        break;
    case OT_FOLDER:            // If the source object is a folder
        break;
    }
    return TRUE;
}
STDMETHODIMP CStore::IsValidObject
(
    HREPLFLD hFolder,    // The handle of the folder where this
                        // item belongs.
    HREPLITEM hItem,        // The handle of the object; could be NULL.
    UINT uFlags            // Reserved; must be 0.
)
{
    CFolder *pFolder = (CFolder *)hFolder;
    CItem   *pItem = (CItem *)hItem;
    if ( pFolder )
    {
        // Check whether hFolder is a valid folder handle.
        if ( pFolder->m_uType != OT_FOLDER )
            return HRESULT_FROM_WIN32( ERROR_INVALID_HANDLE );
    }
    if ( pItem )
    {
        // Check whether hItem is a valid item handle.
        if ( pFolder->m_uType != OT_ITEM )
            return HRESULT_FROM_WIN32( ERROR_INVALID_HANDLE );
         // Search for the item. If the item is not found, return
        // to HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ).

// ...

    }
    return NOERROR;
}
STDMETHODIMP_(void) CStore::FreeObject
(
    HREPLOBJ        hObject        // Handler of the object whose contents need
                            // to be freed.
)
{
    delete (CReplObject *)hObject;
}

The service manager saves the data stored in the HREPLITEM or HREPLFLD handles to Repl.dat. The desktop provider module must implement IReplStore::ObjectToBytes in order to convert an HREPLITEM object or HREPLFLD object into a series of bytes so that the service manager can save the data. The desktop provider module also must implement IReplStore::BytesToObject to convert the same series of bytes back to an object.

When a user connects a device to a desktop, the service manager reads Repl.dat and restores all handles that were used in the previous synchronization. As long as a device is connected, the service manager continues to save handles into Repl.dat.

The following code examples show how to implement IReplStore::ObjectToBytes and IReplStore::BytesToObject.

STDMETHODIMP_(UINT) CStore::ObjectToBytes
(
    HREPLOBJ    hObject,        // The handle to the object.
    LPBYTE      lpb         // Points to a buffer where the array of
                            // bytes should be stored; could be NULL.
)
{
    LPBYTE      lpbStart = lpb;
    CReplObject *pObject = (CReplObject *)hObject;
    CFolder     *pFolder = (CFolder *)pObject;
    CItem       *pItem = (CItem *)pObject;
    if ( lpbStart )
        *lpb = OBJECT_VERSION;
    lpb++;
    if ( lpbStart )
        *(PUINT)lpb = pObject->m_uType;
    lpb += sizeof( pObject->m_uType );
    switch( pObject->m_uType )
    {
    case OT_FOLDER:            // If the object is a folder
        break;
    case OT_ITEM:            // If the object is an item
        if ( lpbStart )
            *(PUINT)lpb = pItem->m_uid;
        lpb += sizeof( pItem->m_uid );
        if ( lpbStart )
            *(FILETIME *)lpb = pItem->m_ftModified;
        lpb += sizeof( pItem->m_ftModified );
        break;
    }
    return lpb - lpbStart;
}
STDMETHODIMP_(HREPLOBJ) CStore::BytesToObject
(
    LPBYTE  lpb,        // Points to a buffer where the array of bytes
                        // should be stored; could be NULL.
    UINT    cb          // The size of the buffer.
)
{
     CReplObject *pObject = NULL;
    CFolder *pFolder;
    CItem *pItem;
    BYTE bVersion = *lpb++;
    UINT uType = *(PUINT)lpb;
    lpb += sizeof( uType );
    if ( bVersion != OBJECT_VERSION )
    {
        // Convert the data based on bVersion.
    }
    switch( uType )
    {
    case OT_FOLDER:        // If the object is a folder
        pObject = pFolder = new CFolder;
        break;
    case OT_ITEM:        // If the object is an item
        pObject = pItem = new CItem;
        pItem->m_uid = *(PUINT)lpb;
        lpb += sizeof( pItem->m_uid );
        pItem->m_ftModified = *(FILETIME *)lpb;
        lpb += sizeof( pItem->m_ftModified );
        break;
    }
    return (HREPLOBJ)pObject;
}


Enumerating Objects

Enumeration is the process of accessing each object in a directory or folder to determine whether it has changed and whether it meets any specified filtering criteria. The enumeration process is as follows:

  1. The service manager calls IReplStore::IsFolderChanged.
  2. If IReplStore::IsFolderChanged returns TRUE, the service manager calls IReplStore::FindFirstItem.
  3. If additional objects exist, IReplStore::FindFirstItem sets *pfExist to TRUE.
  4. The service manager calls IReplStore::FindNextItem repeatedly until *pfExist is FALSE, which indicates that there are no more objects.
  5. The service manager calls IReplStore::FindItemClose to free any resources that were used during enumeration.

Tip If an enumeration takes more than a few seconds to complete, you can use IReplNotify::SetStatusText to have the desktop provider module display text that notifies the user how much time remains until completion.

The following code example shows how to implement IReplStore::FindFirstItem.

STDMETHODIMP CStore::FindFirstItem
(
    HREPLFLD hFolder,    // The handle to a folder.
    HREPLITEM *phItem,    // Output pointer to the handle of the first
                        // item in the folder.
    BOOL *pfExist        // Output pointer to a Boolean value that is set
                        // to TRUE if there is an object in the folder.
)
{
    CFolder *pFolder = (CFolder *)hFolder;

    // Find the first item. pItem used in the following code points to
    // the first item.

// ...

    // Implementation should retrieve a unique identifier from
    // the first item.
    // pItem->m_uid = retrieved_unique_ID;

    // Implementation also should retrieve whatever it is using to         // verify if the first item is changed; for example, a time stamp.
    // pItem->m_ftModified = retrieved_time_stamp;

    *phItem = (HREPLITEM)pItem;

    if ( pfExist )
        *pfExist = TRUE;

    return NOERROR;
}

You can use the desktop provider module to filter synchronization objects during enumeration by returning to the service manager only those objects that meet your filtering criteria. For example, you may want to synchronize only appointments that fall within the next three days. The service manager accesses the filtering criteria—which is stored with the folder handle—by calling IReplStore::IsItemReplicated.

Caution  If a filter is implemented incorrectly, any object that fails the filter criteria appears as a deleted object on the desktop. If the desktop enumeration does not return the object, the service manager assumes that the object is deleted and deletes the corresponding device object. To avoid this, be sure that the desktop provider module returns every object in the store during enumeration.

You can reapply a filter as certain conditions change. For example, you may want to synchronize the appointments falling within the next three days, everyday. This would mean that the date criteria for the filter would change as the days pass. In order to determine exactly what date range to filter for, the service manager calls IReplStore::ReportStatus with RSC_DATE_CHANGED to initiate the synchronization process.


Detecting Desktop Object Changes

The service manager automatically detects object changes and deletions by comparing the list of handles that are returned during enumeration with the handles that are saved in Repl.dat. Before starting an enumeration process, the service manager:

  1. Marks a bit for each handle that is stored in Repl.dat.
  2. Calls IReplStore::FindFirstItem and IReplStore::FindNextItem.
  3. Each time one of these methods return a new handle, the service manager performs a binary search on the handles in Repl.dat in order to find a handle representing the same object.
    • If no matching handle is found, it creates a new object on the desktop store.
    • If a matching handle is found, it clears the bit from the handle in Repl.dat and calls IReplStore::IsItemChanged to see if the object has changed since the last synchronization.

      If the object has changed, it calls IReplStore::CopyObject to copy the data from the returned handle into the handle that is saved in Repl.dat.

  4. Calls IReplStore::IsItemReplicated to see if it should send the object to the device.

All handles in Repl.dat that remain marked once enumeration is complete represent deleted objects.

To hasten the enumeration process, the desktop provider module can detect and report desktop changes to the service manager in real time. To do this:

  1. The service manager calls IReplNotify::OnItemNotify to inform the desktop provider module of the status for an object.
  2. The desktop provider module passes RNC_MODIFIED for a modified object, RNC_CREATED for a created object, and RNC_DELETED for a deleted object.
  3. The desktop provider module passes a handle to the object so that the service manager can search Repl.dat for the corresponding device object.

The sequence of real time notification calls is shown in the following flowchart.

The desktop provider module also can call IReplNotify::OnItemNotify with RNC_SHUTDOWN if it detects that the desktop application has closed. The service manager responds by unloading the desktop provider module and updating the status display.

The following code examples show how to implement IReplStore::IsItemChanged and IReplStore::IsItemReplicated.

STDMETHODIMP_(BOOL) CStore::IsItemChanged
(
    HREPLFLD    hFolder,    // The handle of the folder or the container
                            // that stores the object.
    HREPLITEM   hItem,      // The handle of the object.
    HREPLITEM   hItemComp   // The handle of the object that is used
                            // for comparison.
)
{
    CFolder *pFolder = (CFolder *)hFolder;
    CItem *pItem = (CItem *)hItem;
    CItem *pItemComp = (CItem *)hItemComp;
    BOOL fChanged = FALSE;
    if ( pItemComp )
        fChanged = CompareFileTime( &pItem->m_ftModified,
                                    &pItemComp->m_ftModified );
    else
    {
        FILETIME ft;
        // Read the modification time stamp from the object into ft.
        // Compare it with the time stamp given in the object.
        fChanged = CompareFileTime( &pItem->m_ftModified, &ft );
    }
    return fChanged;
}
STDMETHODIMP_(BOOL) CStore::IsItemReplicated
(
    HREPLFLD    hFolder,     // The handle of the folder or the container
                            // that stores the object.
    HREPLITEM   hItem       // The handle of the object.
)
{
    CFolder *pFolder = (CFolder *)hFolder;
    CItem *pItem = (CItem *)hItem;
    // hItem can be passed NULL.
    if ( pItem == NULL )
        return TRUE;
    // Search for the item; return FALSE if the item is not found.
    // Check if pItem should be replicated by using information stored both in pFolder & pItem. If so, return TRUE.
    return FALSE;
}


Sending and Receiving Objects

You use IReplObjHandler to serialize or convert an object to a series of bytes. It also deserializes an object or converts the bytes back to an object in order to transmit data to a device or desktop. There is no limitation or specification on how to serialize an object. A desktop provider module can serialize the object into any number of bytes, and it can group these bytes into any number of packets.

Whenever an object requires serialization, the service manager calls the IReplObjHandler methods in the following order:

  1. IReplObjHandler::Setup to tell the desktop provider module what object to serialize and to enable the desktop provider module to allocate any resource that are required for serialization.
    1. 2.IReplObjHandler::GetPacket to let the desktop provider module create one or more packets of bytes of any size.

    The service manager calls this method repeatedly until RWRN_LAST_PACKET is returned.

    1. 3.IReplObjHandler::Reset to let the desktop provider module free any used resources.

    The service manager calls this method when serialization is completed.

The service manager does not recognize byte format during serialization. The service manager simply sends the packets in the same byte number and sequence as originally received.

The following flowchart shows the sequence of calls necessary to send and receive data.

The following code example shows how to implement IReplObjHandler::Setup and IReplObjHandler::GetPacket. It also shows the definition for CDataHandler, which is a COM class whose base is IReplStore.

class CDataHandler : public IReplObjHandler
{
public:
    CDataHandler( CStore *pStore );
    ~CDataHandler();
    // ******** IUnknown methods **************
    // AddRef, Release, QueryInterface

    // ******** IReplObjHandler methods **************
    // Setup, Reset, GetPacket, SetPacket, DeleteObj
private:
    long     m_cRef;
     PREPLSETUP m_pWriteSetup, m_pReadSetup;
};
STDMETHODIMP CDataHandler::Setup
(
    PREPLSETUP pSetup    // Points a REPLSETUP structure, which contains // information about the object to be serialized // or deserialized.
)
{
    // Could be reading and writing at the same time, so it is necessary     // to save the pointer to setup struct differently.
    if ( pSetup->fRead )
        m_pReadSetup = pSetup;
    else
        m_pWriteSetup = pSetup;
    return NOERROR;
}
STDMETHODIMP CDataHandler::GetPacket
(
    LPBYTE  *lppbPacket,        // Pointer to the pointer of the outgoing
                            // packet.
    DWORD   *pcbPacket,        // Pointer to a DWORD for the packet size.
    DWORD   cbRecommend         // Recommended maximum size of the packet.
)
{
     if ( m_pReadSetup->hItem == NULL )
        return E_UNEXPECTED;
     // Initialize the packet.

// ...

    // Fill up the packet.

// ...

    // Assign the size of the packet to *pcbPacket and the packet
    // to *lppbPacket.
    //*pcbPacket = sizeof( packet );
    //*lppbPacket = (LPBYTE)&packet;

    return NOERROR;
    //return RWRN_LAST_PACKET if it is the final packet.
}

During synchronization, the service manager creates an instance of IReplObjHandler for each object type. The desktop provider module recognizes when IReplObjHandler::SetPacket is sending information about a new object by checking for RSF_NEW_OBJECT in REPLSETUP::dwFlags. The service manager passes REPLSETUP::dwFlags in the IReplObjHandler::Setup call. The structure REPLSETUP is passed in IReplObjHandler::Setup.

The following table shows the REPLSETUP members that are used by the desktop provider module. All other members are internal to the service manager and should not be changed.

REPLSETUP member

Description

fRead
Set to TRUE for reading an object from the desktop store.

Set to FALSE for writing an object to the desktop store.

dwFlags
 | A collection of bit flags related to serialization and deserialization.

Hfolder
 | A handle to the folder.

HItem
 | A handle to the object to be serialized. The desktop provider module uses the information in this handle to identify and convert the object into packets of bytes.

The process of receiving an object from the device is similar to sending an object to the device. After the packets of data arrive from the device, the service manager calls the IReplObjHandler interface methods, which enables the desktop provider module to convert those packets back into an object. The desktop provider module must take the data packets and create an object. It then must create a new HREPLITEM that represents the object in REPLSETUP::hItem.

The IReplObjhandler methods are always called in the following sequence:

  1. 1.IReplObjHandler::Setup tells the desktop provider module what object to deserialize and enables the desktop provider module to allocate any resource needed for deserialization.
    2.

  2. 2.IReplObjHandler::SetPacket sends packets to the desktop provider module so that it can recreate the object.

    Packets are sent in the exact same number, size, and sequence. The service manager calls this method repeatedly until the last packet is received from the device.

    3.

  3. 3.IReplObjHandler::Reset enables the desktop provider module to free any used resources.

    The service manager calls this method when deserialization is completed

    4.

When the service manager writes an object to the desktop store, it calls IReplStore::UpdateItem. This prompts the desktop provider module to open the object and update the HREPLITEM handle with a time stamp or version number, thus ensuring that the object is not marked as changed on the desktop.

The following code example shows how to implement IReplStore::UpdateItem.

STDMETHODIMP_(void) CStore::UpdateItem
(
    HREPLFLD    hFolder,    // The handle of a folder.
    HREPLITEM   hItemDst,   // The handle of the destination object.
    HREPLITEM   hItemSrc    // A handle to the source object.
)
{
    CFolder *pFolder = (CFolder *)hFolder;
    CItem   *pItemDst = (CItem *)hItemDst;
    CItem   *pItemSrc = (CItem *)hItemSrc;

    if ( pItemSrc )
    {
        pItemDst->m_ftModified = pItemSrc->m_ftModified;
    }
    else
    {
        // Implementation should update whatever it has used to validate
        // object changes, such as a time stamp, by reading it from the         // object.

        // Update the time stamp.
        // pItemDst->m_ftModified = time stamp read from object.
    }
}

Depending on the type of application, a desktop provider module may synchronize objects coming from a device as deletions. For example, when a user deletes an e-mail message on a device, the message is moved to the Deleted Item folder and marked as changed. The desktop provider module receives the changed object and automatically attempts to delete the message on the desktop. In response, IReplObjHandler::SetPacket returns one of the following errors:

  • RERR_DISCARD when the desktop provider module tries to delete the device object immediately after the change is synchronized. The service manager sends a command to the device to delete the corresponding object.
    •

  • RERR_DISCARD_LOCAL when the desktop provider module tries to delete the desktop object immediately after the change is synchronized. The service manager calls IReplObjHandler::DeleteObject to delete the existing desktop object.
    •


Handling Conflicts

A conflict occurs when an object has changed on both the device and the desktop since the last synchronization. To resolve a conflict, the service manager calls IReplObjHandler::GetPacket on the device to send the object to the desktop. The service manager then calls IReplObjHandler::SetPacket on the desktop to create a temporary object. During both the device and the desktop call, the service manager passes RSF_CONFLICT_OBJECT in REPLSETUP::dwFlags.

The following flowchart shows the call sequence for conflict resolution.

After the service manager receives the data from the device, it calls IReplStore::GetConflictInfo and passes a handle to both the original desktop object and the temporary device object. Next, the desktop provider module must fill in the CONFINFO structure to customize the description text displayed in a standard Conflict Resolution dialog box, which is supplied by the service manager.

The following example code shows how to implement IReplStore::GetConflictInfo.

STDMETHODIMP CStore::GetConflictInfo
(
    PCONFINFO pConfInfo         // A pointer to a CONFINFO structure.
)
{
    // Verify that you have the right version of OBJUIDATA.
    if ( pConfInfo->cbStruct != sizeof( CONFINFO ) )
        return E_INVALIDARG;
    // Copy “Stock” to szLocalName and szRemoteName. You can use your
    // own local and remote object names to replace “Stock”.
    lstrcpy( pConfInfo->szLocalName, "Stock" );
    lstrcpy( pConfInfo->szRemoteName, "Stock" );
    CItem   *pLocalItem = (CItem *)pConfInfo->hLocalItem;
    CItem   *pRemoteItem = (CItem *)pConfInfo->hRemoteItem;
    // Find the local object, and then the remote object.
    // pLocalObject points to the local object.
    // pRemoteObject points to the remote object.
    // If both the local and remote objects are found
    if (pLocalObject && pRemoteObject )
    {
        // Compare the local and remote objects.
        // If the local and remote objects are identical
        return RERR_IGNORE;
    }
    // If a local object is found
    if ( pLocalObject )
        // Store information for the local object
        // in pConfInfo->szLocalDesc.
    // If a remote object is found
    if ( pRemoteObject )
        // Store information for the remote object in
        // pConfInfo->szRemoteDesc.

    return NOERROR;
}

If the desktop provider module cannot write a temporary object on the desktop, it can save the packets into memory and return HREPLITEM containing a pointer to the memory location. In this case, the desktop provider module must implement this handle in all methods in IReplStore that accept an HREPLITEM handle, such as CopyObject or FreeObject. When the service manager calls IReplStore::GetConflictInfo, the handle becomes CONFINFO::hRemoteItem. The desktop provider module then can extract descriptive text from the handle and save it into CONFINFO.

The Conflict Resolution dialog box enables the user to:

  • Discard the original desktop object and keep the newly written device object

    If the user chooses to keep the newly written device object, the service manager marks the desktop object as up to date and the device object as changed. This ensures that the device object is transferred to the desktop during the next synchronization.

  • Keep the original desktop object and discard the device object.

    If the user chooses to keep the desktop object, the service manager marks the device object as up to date.

In either case, the service manager calls IReplObjHandler::DeleteObject to delete the temporary object.

Conflict situations do not always require a Conflict Resolution dialog box. The following table describes special error values from IReplStore::GetConflictInfo that resolve the conflict automatically.

Error value
Action
RERR_IGNOR | The desktop provider module compares two handles in CONFINFO, determines that they are identical, and takes no action.
RERR_DISCARD | The service manager detects that the desktop object represented by a handle is already deleted and deletes the device object accordingly.
RERR_DISCARD_LOCAL | The service manager resolves a conflict by deleting a desktop object instead of allowing the desktop provider module to delete the object.


Setting Synchronization Options

For a user to change synchronization options, the desktop provider module must supply a UI that enables the user to select options. There is no limitation or specification for the UI.

A user can gain access to the UI in two ways:

  • If a device is connected to the desktop, the user selects a service provider from the desktop.
  • If the device is not connected to the desktop, the user selects a service provider from the Mobile Device folder.

Once the user selects a service provider, the service manager calls IReplStore::ActivateDialog, which displays the UI for the selected service provider. If the desktop provider module does not support a synchronization options dialog box, the call to IReplStore::ActiveDialog must return E_NOTIMPL. To get a handle to a parent window to display a dialog box or message box, have the desktop provider module call IReplNotify::GetWindow.

If the user chooses to remove a service provider, IReplStore::ActivateDialog returns RERR_UNLOAD. If the user cancels the synchronization options dialog box, IReplStore::ActivateDialog returns RERR_CANCEL.

You can save synchronization options for the desktop provider module either in the registry or in the HREPLFLD handle. If you save synchronization options in a HREPLFLD handle, the desktop provider module sets default option values in IReplStore::GetFolderInfo. If the pointer to which phFolder points is NULL, the desktop provider module must create a new HREPLFLD. If the pointer is not NULL, the desktop provider module loads the options from Repl.dat.


Developing the Device Provider Module

The service manager interfaces with the device provider module to access data. To facilitate this interaction, a device provider module must implement the same IReplObjHandler interface that you use for the desktop provider module. It also must implement the InitObjType, GetObjTypeInfo, ObjectNotify functions. The device provider module can also implement the ReportStatus function, which is optional.

To implement the device provider module

  1. Create one GUID for the store, by using the Visual C++ GUID generator tool, Guidgen.exe.
  2. Initialize the device store.
  3. Enumerate device objects.
  4. Detect device object changes and send and receive device data.

Initializing the Device Store

To initialize the device provider module, call InitObjType. If the device provider module supports synchronization of multiple object types, it calls InitObjType for each object type whose lpszObjType is not NULL. When an ActiveSync service terminates, the lpszObjType of its objects are set to NULL, thereby allowing the device provider module to free any resources it may have allocated.

The following code example shows how to implement InitObjType.

EXTERN_C BOOL InitObjType
(
    LPWSTR lpszObjType,
    IReplObjHandler **ppObjHandler,
    UINT uPartnerBit
)
{
    if ( lpszObjType == NULL )
    {
        // Terminates the device provider module and frees all        // allocated resources.

// ...

        return TRUE;
    }
    // Allocate a new IReplObjHandler.
    *ppObjHandler = new CDataHandler;
    // Save the uPartnerBit so that you can use it later in
    // ObjectNotify.
    // Initializing the module.

// ...

    return TRUE;
}

ActiveSync supports the synchronization of two desktop computers with a Windows CE–based device. To differentiate between two computers, the service manager passes a partner bit into InitObjType when initializing the device provider module. This bit is set to 1 if the connected desktop computer is the first partner and 2 if it is the second partner. The device provider module must use this partner bit when setting and resetting dirty bits of an object.


Enumerating Device Objects

Enumerating objects on a device differs from enumerating objects on a desktop. In contrast to desktop enumeration, the service manager enumerates each object and calls the ObjectNotify function of each device provider module when a user connects a device. If an object on a device needs synchronizing, the device provider module must inform the service manager.


Detecting Device Object Changes

Changes on the device are handled through the ObjectNotify function. The service manager calls ObjectNotify in the following cases:

  • Immediately after connection.
  • When an object changes and the user connects the device.
  • After an acknowledgement is received from the desktop that the object has been synchronized successfully.
  • Two seconds after the device provider module sets ONF_CALLING_BACK.

ObjectNotify checks the flags and the file system identifier information in the OBJNOTIFY structure for a changed object.

The following table shows the values for the OBJNOTIFY::uFlags member.

Uflags member
Definition
ONF_FILE | OBJNOTIFY::oidObject is a file.
ONF_DIRECTORY | OBJNOTIFY::oidObject is a directory.
ONF_RECORD | OBJNOTIFY::oidObject is a record.
ONF_DATABASE | OBJNOTIFY::oidObject is a database.
ONF_CHANGED | The file system object is changed.
ONF_DELETED | The file system object is deleted. Only oidParent in OBJNOTIFY::oidInfo is defined. All other members in OBJNOTIFY::oidInfo are 0.
ONF_CLEAR_CHANGE | The desktop provider module should mark the object as up to date. In this case, OBJNOTIFY::oidObject is the synchronized object identifier, not the Windows CE object identifier.
ONF_CALL_BACK | Set by the device provider module to ask the service manager to call back.
ONF_CALLING_BACK | The service manager sets this flag and calls ObjectNotify.

  • The following code example shows how to implement ObjectNotify.
EXTERN_C BOOL ObjectNotify( POBJNOTIFY pNotify )
{
    // Check to see if the structure size is the same.
    if ( pNotify->cbStruct != sizeof( OBJNOTIFY ) )
         return FALSE;
    // Check ONF_* flags to see if to see if the notification is
    // relevant.
    if ( !( pNotify->uFlags & ONF_DELETED ) )
    {
        // Make sure that you are dealing with the records in your
        // database.
        // The object (a record or a file) must exist.
    }
    if ( pNotify->uFlags & ONF_CLEAR_CHANGE )
    {
        // Check whether the object was changed again
        // during synchronization.
        // If so, return TRUE; if not, return FALSE.
    }
    pNotify->poid = (UINT *)&pNotify->oidObject;
    // Determine what object identifier to return. Consider
    // uPartnerBit in your decision.
    // If you store one object per file and/or record, you simply
    // need to return the file system object identifier. Otherwise,
    // you need to read the file system object and determine the list of
    // object identifiers that have changed.

    return TRUE;
 }

To get information from a database with the oidDataBase object identifier, the service manager calls GetObjTypeInfo.

The following code example shows how to implement GetObjTypeInfo.

EXTERN_C BOOL GetObjTypeInfo
(
POBJTYPEINFO pInfo        // The pointer to the OBJTYPEINFO structure.
)
{
CEOIDINFO   oidInfo;
if ( pInfo->cbStruct != sizeof( OBJTYPEINFO ) )
return FALSE;
    // Clear the structure.
    memset( &(oidInfo), 0, sizeof(oidInfo));
    // Retrieves information about the object in the object store.
CeOidGetInfo( oidDataBase, &oidInfo );
    // Store the database information into the OBJTYPEINFO structure.
wcscpy( pInfo->szName, oidInfo.infDatabase.szDbaseName );
     pInfo->cObjects = oidInfo.infDatabase.wNumRecords;
pInfo->cbAllObj = oidInfo.infDatabase.dwSize;
pInfo->ftLastModified = oidInfo.infDatabase.ftLastModified;
     return TRUE;
}


Registering the Service Provider Module

Registering the Service Provider

For Windows CE Services to recognize a service provider, you must create valid registry entries in the registry on both the Windows-based desktop computer and on the Windows CE–based device. You also must register the object types.

Tip The CEUTIL utility DLL functions are especially helpful when adding desktop registry entries for Windows CE. For more information, see Chapter 7, “Installing and Managing Applications.”

To register a service provider on the Windows-based desktop computer

  1. Provide a programmatic identifier (ProgID) for the desktop provider module.

    The ProgID must be a unique name.

  2. Generate a GUID (Class ID) for the service provider.
  3. Create the following keys in the Windows registry:

    HKEY_CLASSES_ROOT\CLSID\Class ID\InProcServer32

    HKEY_CLASSES_ROOT\CLSID\Class ID\ProgID

    HKEY_CLASSES_ROOT\ProgID\CLSID

The default value of the InProcServer32 key is the full path of the 32-bit DLL that implements the IReplStore interface (for example, for Microsoft Outlook the path is Outstore.dll); the default value of the ProgID key is MS.WinCE.Outlook; and the default value of the CLSID key is the GUID for the store.

After registering the service provider, you must register each object type that the service provider synchronizes. Register the object types in a subdirectory under HKEY_LOCAL_MACHINE. The following screen shot illustrates the desktop registry location for the appointment, contact, and task object types.

Each object type name is a key. Under each key, you must define the following five values:

  • Default. A description of the object type; for example, Outlook Appointment Object.
  • Display Name. The name of the object you want to display; for example, Appointment.
  • Plural Name. The plural name of the object; for example, Appointments.
  • Store. The OLE programmatic identifier, ProgID, of the store that implements the IReplStore and IReplObjHandler interfaces; for example, MS.WinCE.Outlook.
  • Disabled. A value indicating whether the service provider is displayed as disabled or enabled in Windows CE Services. A non-zero value indicates that the service provider is disabled.

The following screen shot illustrates the values defined under the Appointment object type.

Whenever a user connects a new device to the desktop and a new device profile is added to Mobile Device folder, the registry keys for the synchronization objects under HKEY_LOCAL_MACHINE automatically are copied to HKEY_CURRENT_USER.

To register a service provider on the Windows CE–based device

Register the device provider module under HKEY_LOCAL_MACHINE\Windows CE Services\Synchronization\Objects.

The following code shows the proper registration for Microsoft Outlook.

On the device, each object type name is a key, but you only define two values: Store and Display Name. Store refers to the module that exports the functions for this object type, and Display Name refers to the name of the object type.

The following code example shows the device registration for the Appointment object type that was used in the preceding example.

Store    “pegobj.dll”
Display Name    “Appointment”


Managing a Windows CE-Based Device

This chapter discusses how to receive notification when connecting a Windows CE-based device to a desktop PC. Additionally, this chapter discusses how to manage power on a Windows CE-based device.


Receiving Connection Notification

Connection Notification is the method for starting applications automatically when the Windows CE-based device is either connected to or disconnected from the desktop computer. An application must be registered in the desktop system registry for each desired event. Once that is done, the Windows CE Services connection manager starts the application whenever the specified event occurs.

The following applications are registered in the desktop system registry:

  • Windows CE Explorer.
  • Remote tools (for example, the debugger) provided with Windows CE Platform Builder.
  • Connection notification client sample. This sample code is provided with the Windows CE SDK and provides a basic template for receiving connection notifications.

There are two methods to register the desktop application:

  • Registry-based notification using command lines that are registered in the system registry.
  • COM interface-based notification using two Component Object Model (COM) interfaces, one implemented by the connection manager and the other by the application, to perform the registration.


Registry-Based Notification

In registry-based notification, an application places a command line in the desktop system registry in one of two keys. When the event specified by the key occurs, the command line is executed. Registry-based notification is appropriate for applications that do not need some control of the connection manager nor the ability to register and unregister for connection notifications.

The keys used in registry-based notification are HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\AutoStartOnConnect and AutoStartOnDisconnect. When a Windows CE-based device is connected to a desktop computer, the command line under AutoStartOnConnect is executed. Likewise, when the device is disconnected, the command line under AutoStartOnDisconnect is executed.

To register an application for automatic execution

  1. Construct a named value that uniquely identifies the application. It should include a company and product name — for example, MicrosoftHPCExplorerAutoConnect. Enter the named value under the appropriate key, either AutoStartOnConnect or AutoStartOnDisconnect.
  2. Define the named value as the application that is to be executed. Include command line arguments.

The following registry editor (.reg) file shows how to register a command line for both AutoStartOnConnect and AutoStartOnDisconnect. In this example, when the Windows CE-based device is connected, Notepad.exe is started with a command line argument of c:\config.sys. When the device is disconnected, Notepad.exe is started with a command line argument of c:\autoexec.bat.

REGEDIT4
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\AutoStartOnConnect]
"MicrosoftAutoConnectSample"="notepad c:\\config.sys"
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\AutoStartOnDisconnect]
"MicrosoftAutoDisconnectSample"="notepad c:\\autoexec.bat"


COM Interface-Based Notification

The second method you can use to register a desktop application is COM interface-based notification. In this method, two COM interfaces, IDccMan and IDccManSink, are used to register an application in the desktop registry. The Windows CE Services connection manager implements the IDccMan interface, while the application implements the IDccManSink interface. Although the COM interface-based notification method is more complex than registry-based notification, by using it you get some control of the connection manager and the ability to register and unregister for connection notifications.

The Windows CE Services connection manager, which resides on the desktop, displays an icon next to the clock in the taskbar when the Windows CE-based device is connected or is waiting to be connected to the desktop computer. This icon—two terminals with a connecting cable—indicates the connection status. By right-clicking the icon you can start Windows CE Explorer.


Notification and Unregistration Procedures

The notification process follows the same basic steps for connection and disconnection of the Windows CE-based device. The following procedure assumes that the IDccMan and IDccManSink interfaces have been implemented.

  1. Initialize the COM library and register the application for the appropriate event.
  2. Connect or disconnect the Windows CE-based device.
  3. Perform the application processing.

The Connection Notification Client sample application shows several connection notification scenarios, including a new remote connect, a disconnect, and a reconnect. To see the actual sequence of interface method calls for any of these scenarios, build and run the application. Then, view the Notification Messages list box output in the Connection Notification Test dialog box. For a description of this sample, see Windows CE Sample Applications.

To receive notification when the Windows CE-based device connects to a desktop computer

  1. Initialize the COM library and register with the Windows CE Services connection manager.
    1. a.Call the COM function CoInitialize to initialize the Component Object library.
    2. b.Call the COM function CoCreateInstance with the DccMan class identifier (CLSID_DccMan) and IDccMan interface identifier (IID_IDccMan) and receive a pointer to the IDccMan interface. For more information on CLSID_DccMan and IID_IDccMan, see Registering the IDccMan Class Identifier.
    3. c.Call the IDccMan::Advise method, which provides the connection manager with a pointer to the IDccManSink interface that you implemented. It also registers the application with the connection manager.
    4. d.The Windows CE Services connection manager calls the IDccManSink::OnLogInactive method, notifying the application that there is no connection between the desktop computer and the mobile device.
  2. Establish the connection between the desktop and the mobile device.
    1. a.The Windows CE Services connection manager calls the IDccManSink::OnLogListen method. Then, it waits for the remote connection services for both the desktop computer and the Windows CE-based device to respond. Until they are both running, the connection manager will not proceed.
    2. b.For Windows 95-based systems only, the connection manager calls the IDccManSink::OnLogAnswered method when the connection manager has detected the communications interface.
    3. c.The Windows CE Services connection manager calls the IDccManSink::OnLogActive method when the connection is established between the Windows CE-based device and Windows CE Services connection manager.
    4. d.The Windows CE Services connection manager calls the IDccManSink::OnLogIpAddr method, providing the IP address that it obtained for the communications socket.

    Note Only when the IDccManSink::OnLogIpAddr notification occurs is the connection completely established.

  3. Perform the desired processing in the application, which can include processing on the desktop, remote processing on the Windows CE-based device using remote application programming interface (RAPI), or calling the IDccMan methods. However, the application should wait to initialize RAPI, using CeRapiInit, until the IDccManSink::OnLogActive notification is received. This ensures that a connection is established between the desktop computer and the device.

To receive notification when the Windows CE-based device disconnects from the desktop

  1. Initialize the COM library and register the application, as described in the previous section.
  2. Disconnect the device from the desktop computer. Windows CE Services notifies the application when the desktop computer and device are disconnected by calling the IDccManSink::OnLogDisconnection method.
  3. Perform the desired processing in the application. Because there is no connection to the device, this processing can only take place on the desktop computer.

Notification when Reestablishing a Remote Connection

If a connection was established, but then was disconnected by the desktop computer or the Windows CE-based device, the IDccMan::OnLogActive notification occurs when the connection is reestablished.

When an application calls the IDccMan::ShowCommSettings function and the OK button is clicked in the Communications Properties dialog box, the following notification sequence occurs:

  1. IDccManSink::OnLogListen
  2. IDccManSink::OnLogDisconnection
  3. IDccManSink::OnLogInactive
  4. IDccManSink::OnLogListen

If instead the Cancel button is clicked in the dialog box, no notification is sent and a Listen state is maintained.

The Connection Notification Client source code uses the IDccMan interface and implements the IDccManSink interface.

Unregistering an Application

One of the advantages to using the COM interface-based notification process is that it allows an application to unregister itself from being notified. This might be helpful when an application needs to run only once.

To unregister an application from being notified

  1. Call IDccMan::Unadvise, which releases the memory associated with the IDccManSink interface.
  2. Call IDccMan::Release, which releases the IDccMan object.
  3. Call CoUninitialize to perform any OLE cleanup. Note that a call to CoUninitialize is required for each successful call to CoInitialize.


Registering the IDccMan Class Identifier

As mentioned earlier, both the DccMan class identifier, CLSID_DccMan, and the IDccMan interface identifier, IID_IDccMan, are passed in the call to CoCreateInstance. Because the Windows CE Services setup application registers CLSID_DccMan, your application only needs to register IID_IDccMan.

The following example shows the IDccMan class identifier being initialized in the registry.

REGEDIT4
[HKEY_CLASSES_ROOT\CLSID\499c0c20-A766-11cf-8011-00A0c90A8F78]
@="Connection Manager"

[HKEY_CLASSES_ROOT\CLSID\499c0c20-A766-11cf-8011-   /
 00A0c90A8F78\InprocServer32]
@="C:\\Windows\\System\\Rapi.dll"
   "ThreadingModel"="Apartment"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\499c0c20-A766-11cf-8011-   /
 00A0c90A8F78]
@="Connection Manager"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\499c0c20-A766-11cf-8011-   /
 00A0c90A8F78\InprocServer32]
@="C:\\Windows\\System\\Rapi.dll"
   "ThreadingModel" = "Apartment"


Managing Power on a Mobile Platform

The primary function of power management is to increase the battery life of a Windows CE mobile platform. This is accomplished by providing accurate estimates of remaining battery life and notifying the user when the batteries are nearing depletion. Power management for a mobile platform is based on the following assumptions:

  • The platform is used less than two hours per day in bursts from five minutes to one hour.
  • The display is powered 100 percent of the time during use.
  • The platform runs less than 10 percent of the time during typical use.
  • The platform uses both main batteries and a backup battery.
  • The platform has no nonvolatile, writable memory.
  • Maximum battery life is obtained without PC Card or Flash storage devices or those that require very little power from the device batteries. Storage devices drawing appreciable power from the internal batteries significantly reduce battery life.


Power Management States

A mobile platform manages power by allowing the operating system to automatically select one of three operating states, based on user and programming activities. These states are dead, suspend, and on.


Dead State

In the dead state, the device uses no power. It has no batteries, or all batteries are completely dead. All contents of RAM are lost. The user purchases the platform in this state.


Suspend State

In the suspend state, the platform uses minimal power to maintain its clock, its applications, and the persistent data stored in RAM. To reduce power requirements, the platform removes power from unneeded circuits and devices, such as the keyboard decoder, display, scratch-pad memory, and processor. A PC Card storage device driver determines the power that it uses when the device is in the suspend state. The processor might take as long as 100 milliseconds to wake up from this state.

The platform switches to the suspend state when the following events occur:

  • The user selects the Suspend command.
  • The device detects a critical power condition.
  • The activity timer performs a time-out.

The operating system uses an activity timer to gauge whether the user is actively using the platform. The timer counts down while the platform is in the on state. When the timer reaches zero, the platform switches to the suspend state. The user can set the maximum value of this timer, in seconds. Two events reset the timer to its maximum value: a key press or release or a touch event. When the timer reaches five seconds, the platform sounds a warning to alert the user that it is about to suspend operation. The operating system does not notify applications when it enters the suspend state because most applications are not affected by this transition. When the platform resumes operation, applications also resume operation as if they had been continuously running.


Programming the Suspend State

Applications generally do not run while a platform is in the suspend state. However, several functions can alter the time at which a platform suspends itself. The following table shows functions that an application can use to reset activity times or to set timers and events that can switch the platform from the suspend state to the on state.

Function
Description
CeRunAppAtTime | Sets a timer or runs an application at a specified time.
WaitCommEvent | Waits for a communications event to occur. Serial, Infrared Data Association (IrDA), or PC Card storage devices can return EV_POWER when the device resumes operation.

If an application uses the Sleep function, it does not increment the elapsed time counter while the platform is in suspend state. It starts counting again when platform operation resumes. The internal clock is not affected when platform operation is suspended. Though an application is not notified when it enters the suspend state, the operating system does notify a device driver by calling a power-handler function. A device driver might use this notification to suspend operation of the device it controls, which may cause a device not to be available immediately after the device switches to the on state. For example, when a file stored on a PC Card storage device is open and the user tries to save the file before the card is turned on, the system returns an error message.


On State

In the on state, the platform uses more power than in the dead or suspend states. Any time the display, keyboard, or touch screen are active, the platform is in the on state. When in the on state, the platform can switch between two processor modes: full speed and idle. In full-speed mode, the processor runs at normal operating frequency. In idle mode, the internal processor clock stops, and the processor uses little power. Peripherals and DRAM may be on or off. The processor can enter and exit this state in approximately 10 milliseconds. The platform switches to the on state when the following events take place:

  • The user presses the On button.
  • The user triggers an alarm event.
  • The user performs a warm or cold boot.
  • The user changes the battery.

In the on state, the default mode is full speed. The platform switches from full-speed mode to idle mode when all processes are idle. Switching to idle mode is transparent to the user and most applications because the system continues to process interrupts, including the time-slice interrupt.


Programming in the On State

When the platform is in the on state, some applications may prevent the operating system from switching the platform to the idle mode. These applications retain control by using idle loops or by using functions that do not let the operating system block operation. All applications can enhance power management by using functions that let the operating system block a return to the application. For example, use the GetMessage function, rather than the PeekMessage function, because GetMessage lets the system block a return to the application, and PeekMessage does not. Letting the operating system retain control allows it to determine when the system is inactive so that it can select the most efficient operating state for the platform.

If you are programming a power-critical application, use the GetPowerStatusEx function to determine the amount of power remaining on your platform. This function returns a variety of information regarding power status, including the amount of remaining power, how long the current charge will last, and recharging status.


Resuming Operation, Rebooting, and Resetting

Resuming device operation from the on-state idle mode or the suspend state does not alter platform memory or change application settings. In contrast, rebooting or resetting the platform does alter memory and affect application operation. The following table shows the differences between these transitions.

Transition
Description
Wake up
or resume 		Transitions from the suspend state to the on-state full-speed mode. Wake up does not change memory or application settings.
Cold boot
or cold reset 		Resets the platform completely. All applications are terminated, the working memory is cleared, and the object store is cleared.
Power-on reset Transitions from the dead state to the on state. Power-on reset has the same consequences as a cold boot.
Warm boot
or warm reset 		Terminates all applications and clears working memory. The object store integrity is maintained.

The cold boot and power-on reset transitions occur when power is first applied to the platform or when all power is removed from the platform and then reapplied after one minute. Unless the platform uses nonvolatile memory, the user will have to load or reload applications after a power-on reset. Object store data is lost after a cold boot.

A warm boot occurs when the user presses the reset button on the platform. This button is placed in a pin-hole recess or under the battery cover to protect it from accidental activation. Users perform a warm boot only when the platform has stopped responding or has become unstable. A warm boot stops applications that are running and clears the memory used for application execution. Memory used for the object store and file system is preserved.


Managing the Desktop

This chapter discuses how to invoke functions from a desktop PC and how to transfer files between a desktop PC and a Windows CE-based device.


Invoking Functions from a Desktop Computer

Windows CE supports a remote application programming interface (RAPI) that gives an application running on a desktop computer the ability to invoke function calls on a Windows CE-based platform. The desktop computer is the RAPI client and the Windows CE-based platform is the RAPI server. The communication uses Windows Sockets (Winsock) and can take place over a serial link, a modem connection, or a network connection.

The function calls behave much like the equivalent Windows CE functions. For the most part, RAPI functions have the same syntax, parameters, and return values as the corresponding Windows CE versions. Any differences are noted in the reference documentation for the RAPI functions.

Note String and character parameters must be in Unicode format. Use the appropriate conversion routines, if necessary.


Initializing and Terminating Remote Application Programming Interface

Before making RAPI calls, you must call the CeRapiInit or CeRapiInitEx function. These functions perform routine initialization and set up the communications link between the desktop computer and the platform.

The CeRapiInit call is a synchronous operation. It does not return control to the application until a connection is made or an error occurs. In contrast, the CeRapiInitEx call is an asynchronous operation and it returns immediately. CeRapiInitEx continues the initialization until a connection is made, an error occurs, or there is a call to CeRapiUninit. Although CeRapiInitEx avoids blocking any threads, it is a more complicated method of initialization.

To initialize RAPI using CeRapiInitEx

  1. Call CeRapiInitEx.
  2. If an error is returned, exit.
  3. If successful, call WaitForSingleObject or WaitForMultipleObjects to wait on the event handle passed back in the heRapiInit member of RAPIINIT.
  4. When heRapiInit is signaled, check for a successful connection or an error value.
  5. Check the hrRapiInit member of the RAPIINIT structure for the final return value.

When you are finished with RAPI, call CeRapiUninit to terminate the connection and perform any necessary cleanup. Because creating and terminating connections are fairly expensive operations, establish and terminate the link only once per session, not on a per-call basis.

The following code example shows how to use the CeRapiInitEx function. Following the CeRapiInitEx call, the MsgWaitForMultipleObjects function is used to wait on one of two events. The first event is when the event handle is passed back through the heRapiInit member of the RAPIINIT structure. The second event is when a user terminates a connection.

HRESULT InitRapi(HEVENT hExit)
{
   RAPIINIT ri = { sizeof(RAPIINIT) };
   HRESULT hr = CeRapiInitEx(&ri);
   if ( FAILED( hr ) )
   {
      return(hr);
   }

   HANDLE ahWait[] = { hExit                  , ri.heRapiInit };
   enum              { WAIT_EXIT=WAIT_OBJECT_0, WAIT_INIT     };

   DWORD dwObj = WaitAndDispatch(ARRAYSIZE(ahWait), ahWait);

   if (WAIT_INIT == dwObj)            //Event signaled by RAPI
   {
      if (FAILED(ri.hrRapiInit))      //Connection failed
      {
         CeRapiUninit();
      }

      return(ri.hrRapiInit);
   }

                           //Event signaled by user or timeout occurred
      CeRapiUninit();

      if (WAIT_EXIT == dwObj)
      {
         return(HRESULT_FROM_WIN32(ERROR_CANCELLED));
      }

      return(E_FAIL);
}

enum
{
   WAD_ALLINPUT    = 0x0000,
   WAD_SENDMESSAGE = 0x0001,
} ;

DWORD WaitAndDispatch(DWORD nCount, HANDLE *phWait, DWORD dwTimeout, UINT uFlags)
{
   DWORD dwObj;
   DWORD dwStart = GetTickCount();
   DWORD dwTimeLeft = dwTimeout;

   for ( ; ; )
   {
      dwObj = MsgWaitForMultipleObjects(nCount, phWait, FALSE,
         dwTimeLeft, (uFlags&WAD_SENDMESSAGE) ? QS_SENDMESSAGE : QS_ALLINPUT);
      if (dwObj == (DWORD)-1)
      {
         dwObj = WaitForMultipleObjects(nCount, phWait, FALSE, 100);
         if (dwObj == (DWORD)-1)
         {
            break;
         }
        }
        else if (dwObj == WAIT_TIMEOUT)
        {
            break;
        }

      if ((UINT)(dwObj-WAIT_OBJECT_0) < nCount)
      {
         break;
      }

      MSG msg;
      if (uFlags & WAD_SENDMESSAGE)
      {
         PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
      }
      else
      {
         while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
         {
            DispatchMessage(&msg);
         }
      }

      if (INFINITE != dwTimeout)
      {
         dwTimeLeft = dwTimeout - (GetTickCount() - dwStart);
         if ((int)dwTimeLeft < 0)
         {
            break;
         }
      }
   }

   return(dwObj);
}


Executing Functions and Applications

Among all RAPI functions, there are two functions that execute functions and applications residing on the Windows CE-based platform:

  • CeCreateProcess

    This function creates a new process that runs a specified executable file residing on the Windows CE-based platform.

  • CeRapiInvoke

    This function remotely executes a function residing on the Windows CE-based platform and provides for both input parameters and output data. It operates in either of two modes: block, known as synchronous, or stream, known as asynchronous.

    In block mode, the caller passes both input parameters and output data in a single buffer. Because this is a synchronous call, all input data must be present in memory at the time of the call and all output data must be present before the function finishes.

    In stream mode, an IStream type interface is used to exchange arbitrarily-sized data in any order and direction. The caller can pass input data in a single buffer, but from that point on all data should be exchanged through the stream. Because the data can be read, written, and stored in chunks, stream code is significantly faster than block mode. The interface used is based on Istream, but has two additional methods to allow you to do timeouts.

Note LocalAlloc allocates the memory passed for both the pInput and ppOutput parameters of CeRapiInvoke. The called function frees the input memory allocation, and the calling application frees the output memory allocation.


Retrieving Information

To retrieve path information

  • Call the CeGetTempPath function to get the path of the directory that is designated for temporary files.

    – Or –

    Call the CeGetSpecialFolderPath function to get the path of a specific desktop folder, which depends on the input parameter. The possibilities include the recycle bin, Start menu directory, document template directory, network directory, and folders for fonts or installed printers.

To retrieve other information

  • Call the CeFindAllDatabases function to get information about all databases of a specified type.

    – Or –

    Call the CeFindAllFiles function to get information about all files and directories in a specified directory of the Windows CE object store.

In both cases, the information is returned in an array of CE_FIND_DATA structures.

You must free the memory allocated by the CeFindAllDatabases, CeFindAllFiles, or CeReadRecordProps function by calling the CeRapiFreeBuffer function.


Handling RAPI Errors

In addition to errors associated with their non-RAPI counterparts, RAPI functions can fail because of RAPI-related errors. Network errors, for example, will need to be communicated back to the calling application.

RAPI functions that fail due to a RAPI-related error will return the error value defined for their Win32-based counterpart. To distinguish between RAPI and non-RAPI errors, use either the CeRapiGetError function or the CeGetLastError function. To determine if a function failed because of RAPI errors, call CeRapiGetError. To determine if a function failed because of non-RAPI errors, call CeGetLastError, which works the same as the GetLastError function does on Windows-based platforms.


Sample RAPI Application

The following code example shows the basics of initializing the RAPI client, making calls, and handling errors.

#include <stdio.h>
#include <rapi.h>
#include <string.h>

void PrintDirectory( LPWSTR Path, UINT Indent )
{
   if ( !Path )
      return;

   DWORD          foundCount;
   LPCE_FIND_DATA findDataArray;

   WCHAR searchPath[MAX_PATH];
   wcscpy( searchPath, Path );
   wcscat( searchPath, L"*" );

   if ( !CeFindAllFiles( searchPath,
                           FAF_ATTRIBUTES | FAF_NAME,
                           &foundCount,
                           &findDataArray ) )
   {
      printf( "*** CeFindAllFiles failed. ***\n" );

      if ( CeGetLastError() != ERROR_SUCCESS )
         printf( "failure occurred on the HPC function\n" );

      return;
   }

   if ( !foundCount )
   {
      for ( UINT indCount = 0 ; indCount < Indent ; indCount++ )
         printf( "  " );
      printf( "No files found.\n" );
      return;
   }

   for ( UINT i = 0 ; i < foundCount ; i++ )
   {
      for ( UINT indCount = 0 ; indCount < Indent ; indCount++ )
         printf( "  " );
      wprintf( findDataArray[i].cFileName );
      printf( "\n" );

      if ( findDataArray[i].dwFileAttributes &
FILE_ATTRIBUTE_DIRECTORY )
      {
         WCHAR newPath[MAX_PATH];

         wcscpy( newPath, Path );
         wcscat( newPath, findDataArray[i].cFileName );
         wcscat( newPath, L"\\" );
         PrintDirectory( newPath, Indent + 1 );
      }
   }

   CeRapiFreeBuffer( findDataArray );
}

void main()
{
   HRESULT hr = CeRapiInit();
   if ( FAILED(hr) )
   {
      printf( "*** CeRapiInit() failed. ***\n" );
      return;
   }

   PrintDirectory( L"\\", 0 );

   CeRapiUninit();

}

Transferring Files

A file filter is a dynamic-link library (DLL) that controls the transfer of data between the desktop computer and the Windows CE-based device. File filters are used by the Windows CE Services application on the desktop computer to automatically convert files as they are transferred.

File formats used by the Windows CE operating system and Windows CE-based applications are generally different from those of the corresponding Windows-based applications. For example, Pocket Word does not support OLE compound files. Windows CE Services automatically adjusts file formats as files are transferred between the desktop computer and the Windows CE-based device.

Windows CE Services includes the following common filters:

  • Pocket Word (.pwd) to Microsoft® Word (.doc)
  • Microsoft Word (.doc) to Pocket Word (.pwd)
  • Pocket Excel (.pxl) to Microsoft® Excel 5.0 (.xls)
  • Microsoft Excel (.xls) to Pocket Excel (.pxl)
  • Windows bitmap (.bmp) to Windows CE 4-color bitmap (.2bp)

You can extend the file-filtering capability of Windows CE Services by defining your own application-specific filters. This section describes file filters and the interfaces used to create them.

Implementing a file filter is similar for importing and exporting files. The only differences are in the registry settings and in how the body of the file filter—the converter function—changes data. The examples in this section demonstrate the procedure for importing files, but typically you would write a converter function that handles both importing and exporting, using dual registry settings that indicate both the import and export functionality.

Note The words “importing” and “exporting” in this chapter are from the perspective of the Windows CE-based device. Thus, importing a file with a file filter transfers a file from the desktop computer to the device, whereas exporting a file with a file filter transfers a file from the device to the desktop.


Registering File Types and File Filters

Windows CE Services uses the registry entries to determine which conversions are available for a given file type and how to invoke the filter that supports the conversion. For this reason, you must register each file type and file filter properly using the following procedure.

To register file types and their filters

  1. Register the file extension type.
  2. Generate a class identifier (CLSID) for the file filter.
  3. Register the file filter.

The following sections describe each step in detail.

Note CEUTIL, a utility DLL, has functions that are especially helpful when dealing with the desktop registry entries for Windows CE Services. For information about CEUTIL, see Installing and Managing Applications.


Registering a File Extension Type

Windows CE Explorer, like Windows Explorer, allows you to customize a type name, as displayed in the details view of Explorer, and an icon for any file extension, for example, .pwd. File filters must be registered under HKEY_CLASSES_ROOT.

The following is the structure of HKEY_CLASSES_ROOT:

HKEY_CLASSES_ROOT\.<file extension>
   \(Default) = <Class Name>
HKEY_CLASSES_ROOT\<Class Name>
   \(Default) = <Name to be displayed in the “Type” column of Explorer>
   \DefaultIcon = <filename or index of the icon for this type>


Generating a Class Identifier for a File Filter

Every file filter must be given a unique CLSID, which identifies class objects to OLE. CLSIDs are universally unique identifiers (UUIDs), also called globally unique identifiers (GUIDs). The file filter’s CLSID must be included in your application and it must be registered with the operating system when your application is installed.

If the file filter supports both importing and exporting, a unique CLSID must be associated with each file filter that is to be registered for the respective import and export registry setting.

The GUID Generator tool lets you generate a GUID that you can use to identify your file filter. A GUID Generator application, named Guidgen.exe, is provided with Microsoft Visual C++. The GUID Generator calls the CoCreateGuid function to generate a new GUID. It also lets you copy the GUID to the clipboard for insertion into your application’s source code using one of the following formats:

  • IMPLEMENT_OLECREATE macro format

    Defined in an IMPLEMENT_OLECREATE macro, which allows instances of a CCmdTarget-derived class to be created by Automation clients. For example:

    // {CA761230-ED42-11CE-BACD-00AA0057B223}
IMPLEMENT_OLECREATE(<<class>>, <<external_name>>,
0xca761230, 0xed42, 0x11ce, 0xba, 0xcd, 0x0, 0xaa,
0x0, 0x57, 0xb2, 0x23);
  • DEFINE_GUID macro format

    Defined in an IMPLEMENT_OLECREATE macro, which is included with Microsoft Visual C++ in the file Afxdisip.h. It allows instances of a CCmdTarget-derived class to be created by Automation clients. For example:

    // {CA761230-ED42-11CE-BACD-00AA0057B223}
DEFINE_GUID(<<name>>,
0xca761230, 0xed42, 0x11ce, 0xba, 0xcd, 0x0, 0xaa, 0x0, 0x57, 0xb2, 0x23);
  • Statically allocated structure format

    Declared as a statically allocated structure. For example:

    // {CA761232-ED42-11CE-BACD-00AA0057B223}
static const GUID <<name>> = { 0xca761232, 0xed42, 0x11ce,
{ 0xba, 0xcd, 0x0, 0xaa, 0x0, 0x57, 0xb2, 0x23 } };
  • Registry entryf

    Specified in a form suitable for registry entries or registry editor scripts. For example:

    {CA761233-ED42-11CE-BACD-00AA0057B223}


Registering a File Filter

An application registers a file filter by placing its CLSID in the following locations:

  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\Filters\.<file extension>\InstalledFilters key. This registration associates the file filter with the file type it converts.
  • HKEY_CLASSES_ROOT\CLSID key. This registration provides information on the file filter’s capabilities and its DLL.

Registering a file filter in two locations guarantees that your file filter is available for any new device partnership. Windows CE Services uses device partnerships to allow multiple devices to connect to the same desktop. If device partnerships exist when an application registers a file filter, an application must create a file filter extension key for each existing device partnership. Connecting a device to a desktop device for the first time establishes a new device partnership with a unique identifier. Desktop devices use the partnership identifier to store unique settings for synchronization, file conversions, and backup-and-restore information.

All filters registered under the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\Filters key are copied to the HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows CE Services\Partners\<partner identifier> key where <partner identifier> is the identifier of the new partnership.

A file type can have more than one file filter associated with it. The Windows CE Services user interface lists the registered filters as filter options. A file filter can be registered under the file type’s \InstalledFilters key in the DefaultImport or DefaultExport subkeys. As their names imply, these subkeys define the default file filters for the file type. The file filter specified under the extension key as the DefaultImport or DefaultExport value will be shown as the default.

Note Any filter defined as the DefaultImport or DefaultExport value must be an InstalledFilters value, also.

A file filter can also be registered for a particular device. For example, for a Palm-sized PC, the file filter extension subkey needs to be created under the HKEY_LOCAL_MACHINE\SOFTWARE\Windows CE Services\SpecialDefaults\Palm PC\Filters key.

The following is the structure of the Filters key and the InstalledFilters subkey.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\Filters

   \.<file extension>
      [DefaultImport = <Default import filter CLSID>]
      [DefaultExport = <Default export filter CLSID>]
      \InstalledFilters
         [<clsid1>]
         . . .
         [more CLSID's for this extension]
    . . .
    [more extensions]

The HKEY_CLASSES_ROOT\CLSID key gives basic information concerning file filters. Each file filter that has been identified in the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\Filters key must be registered in this key. The following is the key’s structure and its subkeys.

HKEY_CLASSES_ROOT\CLSID

   \<clsid>
      \(Default) = <Description in "Edit Conversion Settings" listbox>
      \DefaultIcon = <filename,index for the icon for this type>
      \InProcServer32 = <filename of the DLL that handles his type>
         ThreadingModel = Apartment
      \PegasusFilter
         [Import]
         [HasOptions]
         Description = <String to display in the conversion dialog>
         NewExtension = <extension of converted file>
   \. . . [more clsids for filters]

The clsid key is a named value that is the CLSID of the registered file filter. This key contains the following subkeys:

  • DefaultIcon

    Defines the icon name string or icon resource identifier for the icon associated with the file filter DLL.

  • InProcServer32

    Identifies the file filter DLL using the default value, and defines the Apartment model capabilities of the file filter in the ThreadingModel named value.

  • PegasusFilter

    Provides information on the specific capabilities of the file filer. The following table shows possible named values for this key.

Named Value
Description
Import | If this named value exists, the conversion type is for importing files from the desktop computer to the Windows CE-based device. Otherwise, the conversion type is for exporting files from the desktop computer to the Windows CE-based device.
HasOptions | If this named value exists, the file filter supports the ICeFileFilter::FilterOptions method.
Description The data for this named value is a string that describes the conversion. Windows CE Services displays this text on the property sheets displayed by selecting the DeviceDesktop or DesktopDevice tab control selections from the File Conversion Properties dialog box and then clicking the Edit button to display the Edit Conversion Settings dialog box.

For example, if the Import named value exists, then, on the DesktopDevice property sheet, the data value defined by the Description named value will be displayed under the file conversions details “Convert to HPC files of the type.”

NewExtension | Defines the extension of the file that will be created on the destination device.


Sample File Filter Registry Entry

The following is a sample registry editor (.reg) file used to register the Bitmap Image file filter converter. It is to be used when a bitmap file is imported from a desktop computer to a Windows CE-based device. The sample file can be used to convert a bitmap file with a .bmp format to a bitmap file with the .2bp format used by Windows CE. The last three entries register the .2bp file extension to be displayed with a specific icon and name.

Note The 2bp.dll file converter is registered and installed when Windows CE Services is installed on the desktop computer.

REGEDIT4

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\Filters\.bmp]
"DefaultImport"="{DA01ED80-97E8-11cf-8011-00A0C90A8F78}"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\Filters\.bmp\InstalledFilters]
"{DA01ED80-97E8-11cf-8011-00A0C90A8F78}"=""

[HKEY_CLASSES_ROOT\CLSID\{DA01ED80-97E8-11cf-8011-00A0C90A8F78}]
@="Bitmap Image"

[HKEY_CLASSES_ROOT\CLSID\{DA01ED80-97E8-11cf-8011-00A0C90A8F78}\DefaultIcon]
@="c:\Program Files\Windows CE Services\2bp.dll,-1000"

[HKEY_CLASSES_ROOT\CLSID\{DA01ED80-97E8-11cf-8011-00A0C90A8F78}\InProcServer32]
@="2bp.dll"
"ThreadingModel"="Apartment"

[HKEY_CLASSES_ROOT\CLSID\{DA01ED80-97E8-11cf-8011-00A0C90A8F78}\PegasusFilter]
"Import"=""
"Description"="Bitmap Image."
"NewExtension"="2bp"

[HKEY_CLASSES_ROOT\.2bp]
@="2bpfile"

[HKEY_CLASSES_ROOT\2bpfile]
@="Bitmap Image"

[HKEY_CLASSES_ROOT\2bpfile\DefaultIcon]
@="c:\Program Files\Windows CE Services\minshell.dll,-2025"


Implementing and Using a File Filter

The Windows CE SDK includes a sample file filter named Copyfilt that imports a binary file (.bin) from a desktop computer to a binary file (.pbn) on a Windows CE-based device. The Copyfilt sample file filter demonstrates basic operations for implementing a file filter, which are described in the following procedure.

To implement a file filter

  1. Register the file filter DLL.

    See Registering a File Filter.

  2. Implement the ICeFileFilter interface and methods.

    For a list of file filter interfaces, see Lists of Functions and Interfaces.

  3. Windows CE Services calls the QueryInterface method for the file filter’s ICeFileFilterOptions interface. If this interface is available, it then calls the ICeFileFilterOptions::SetFilterOptions method with a correctly initialized CFF_CONVERTOPTIONS structure. The bNoModalUI member specifies whether the converter is allowed to bring up modal UI while performing the conversion.

    For a file filter that includes selectable conversion options, the ICeFileFilter::FilterOptions method should be implemented. This allows a user to select among the conversion options supported by the file filter.

To use a file filter

  1. The user uses the drag-and-drop method to transfer a file between Windows CE Explorer on the desktop and on the device.
  2. Windows CE Services prompts the user for a conversion type, using the File Conversion Properties dialog box.
  3. Windows CE Services calls the file filter’s ICeFileFilter::NextConvertFile method to perform the custom file conversion. Information about the file conversion and about the source and destination files is passed by pointers to the CFF_CONVERTINFO, CFF_DESTINATIONFILE, and CFF_SOURCEFILE structures.

    Within the ICeFileFilter::NextConvertFile method:

    1. a.Call ICeFileFilterSite::OpenSourceFile to open the source file.
    2. b.Call ICeFileFilterSite::OpenDestinationFile to open the destination file.
    3. c.Read data from the stream file that was opened using the OpenSourceFile method.
    4. d.Convert the data. This can include ISV-developed code and remote application programming interface (RAPI) calls.
    5. e.Check on the status of the NextConvertFile pbCancel parameter occasionally to ensure that the user has not aborted the conversion process. If the conversion has been aborted, perform all cleanup operations and exit.
    6. f.Write the converted data to the stream file that was opened using the OpenDestinationFile method.
    7. g.Report the progress of the file conversion by occasionally calling the ICeFileFilterSite::ReportProgress method. Windows CE Services uses this information to update a status bar showing the percentage completion of the conversion. You should limit your use of this method because it can add substantially to the conversion time.
    8. h.Report data that is intentionally discarded during conversion by calling the ICeFileFilterSite::ReportLoss method. Windows CE Services displays a message with this information when the file conversion is complete. Depending on the error format passed in the call, Windows CE Services may call the file filter’s ICeFileFilter::FormatMessage method, in order to properly format the message.
    9. i.Close the source file, using the ICeFileFilterSite::CloseSourceFile method, and then close the destination file, using the ICeFileFilterSite::CloseDestinationFile method.


Using Remote API Calls in a File Filter

It is possible to use RAPI calls in a file filter. This allows use of any RAPI functions that are appropriate to your application, such as registry or file functions.

Do not initialize RAPI in the file filter DLL by using CeRapiInit. Rather, the NextConvertFile method should have already performed the RAPI initialization and established a connection between the desktop computer and the Windows CE-based device. If a RAPI call fails because there is no connection established, the file converter should perform some type of default action rather than just failing. For example, this default action could involve querying the user to select from various options.

To determine if a call failed due to a failure in the RAPI, use CeRapiGetError. To diagnose non-RAPI related errors, use CeGetLastError.

For more information on RAPI, see Invoking Functions from a Desktop Computer.


Filter-Defined Error Values

There are two ways that file filter errors are returned. First, the NextConvertFile method uses the HRESULT_FROM_WIN32 macro to return an HRESULT error value if the method fails.

But, there is a way to have an error value that is customized to your data type. The NextConvertFile function can return a filter-defined error value in the variable pointed to by the perr parameter. To use this method, the error value must be defined by using the CF_DECLARE_ERROR macro defined in the Replerr.h header file. Also, the filter DLL must include a message table that contains the error value and a corresponding message string. When Windows CE Services gets the filter-defined error value, it then uses the Win32 FormatMessage function to create the error string, using ICeFileFilter::FormatMessage to check the filter object first for the relevant string (). For more information about message tables, see the Microsoft Platform SDK.


Implementing a Dummy File Filter

A dummy file filter gives the appearance that files are being converted without actually implementing a file filter or performing a filter conversion. Instead, the file is passed without any conversion whatsoever.

Implementing such a dummy filter may be desirable for a file that has a unique file type or one that has not been registered already in the desktop registry. It might also be useful for a file that does not need any conversion when it is transferred between Windows Explorer on the desktop computer and Explorer for the device.

Usually, if a file with an unregistered file type is copied to the device, the device will display the warning No Converter Selected. This warns the user that the file will be transferred without conversion. In this situation, implementing a dummy filter would avoid alarming the user with the file conversion warning.

Note The No Converter Selected warning will be displayed only if the mobile device’s File Conversions Properties is set to enable file conversion. If the Enable File Conversion check box is unchecked, then the No Converter Selected warning will not be displayed.

To register a dummy file filter

  1. Modify HKLM\Software\Microsoft\Windows CE Services\Filters, the desktop computer registry key, by adding a subkey. This subkey should name the file extension for the type of files that should be converted using the NULL file conversion. For example, if you are converting files with extension .abc, then you must add a subkey .abc.
  2. Under the .abc subkey, create a string value named DefaultImport that is set to Binary Copy. This string value identifies the conversion for files with .abc extensions that are imported from the desktop computer to the Windows CE-based device.
  3. Under the .abc subkey, create a string value named DefaultExport that is set to Binary Copy. This identifies the conversion for files with .abc extensions that are exported from the device to the desktop computer.

The following registry editor (.reg) file registers the example .abc dummy file filter.

REGEDIT4
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\Filters\.abc]
"DefaultImport"="Binary Copy"
"DefaultExport"="Binary Copy"

In this example, when a file with an .abc extension is copied between the desktop computer and the Windows CE-based device, it will seem as though a conversion process is taking place because you do not receive the warning No Converter Selected from Windows CE Services. However, no filter actually is being used, because an InstalledFilters subkey has not been added under the .abc key.


Installing and Managing Applications

This chapter is going to discuss how to use one application to increase the capabilities of another application. First, we'll talk about how applications run on a Windows CE device. Then, we'll talk about how to install your own applications using the Windows CE CAB files and the Application Manager. Then, we'll talk about how to use two of the applications that are common to many Windows CE Devices: the Help system, the spell checker, and (maybe) Pocket Outlook.


Running an Application on Windows CE

How it all fits together: talk about how applications run in Windows CE. Give a nice graphic to spiffy it out. Also talk about how connections and transfers work into it, as well. This section is in here for developers that haven't really done much developing before can get a grip on what's going on.

Projected size: 2 pages

Artwork: conceptual shot of how everything fits together. Also have conceptual shot of how a computer and a CE devices syncs.

Resources: Developers, my Operating Systems Concepts textbook, the file transfer chapters.


Installing an Application

Introduction: discuss how the desktop platform and the Windows CE device interact when downloading an application. Specifically, show how the CAB Wizard and Application Manager work together to get an application correctly installed and uninstalled on a device.

projected size: 1 page

Artwork: 1 conceptual shot showing how the CAB Wizard and Application Manager work together.

Resources: the current chapter, Win32 docs, and the developers.


Using the CAB Wizard

How and when to call the CAB Wizard during the developmental process, and what it does.

Projected size: 1 page

Resources: the current docs, the Win32 docs, and the developers

Artwork: none


Writing an .inf File

This section contains the definition of a CAB .inf file. It pretty much looks like a Reference entry, only it's really big. The only thing I think I'll add to the current content is some transition material at the beginning and ending.

Projected size: 7 pages

Resources: the current docs

Artwork: none


Sample .inf File

An intro paragraph plus an example of what an .inf file looks like

Size: 2 pages

Art: none

resources: already written in the book


Writing a Setup .dll

The Setup .dll lets a developer add unique capabilities to the Setup routine. Here's how you can write one. This section will give a brief overview of what you can do with the Setup .dll, and then point the developer off to the functions that this .dll exports. It will also point off to a different section that discusses how to build a .dll file.

Size: 1 page

Artwork: none

resources: it's already written in the chapter


Troubleshooting CAB Wizard

List o' things that may go wrong when you create a CAB Wizard file.

Projected size: ½ page

Artwork: none

Resources: already in the chapter


Managing an Application

Once you have created your CAB file, you need to install the application on the Windows CE-based device. The following sections discuss how to write and call Application Manager.

Projected size: ½ page

Artwork: none

Resources: already in the chapter (this is basically a transition section)


Writing an .ini File for Application Manager

General introduction to what an .ini file is in relation to Application Manager.

Projected size: ½ page

Artwork: none

Resources: already in the chapter


.ini File Format for Application Manager

This is a big 'ol Reference entry for an .ini file for Application Manager.

Projected size: 1 ½ pages

Artwork: none

Resources: already in the chapter


Sample Application Manager .ini File

I'll add some intro material, but this is pretty much a sample. The reason it has its own section is to distinguish it from the material in the previous heading (they look really similar).

Projected size: ½ page

resources: already in the chapter


Installing and Removing an Application with Application Manager

Here's how to call the Application Manager to install and remove an application.

Projected size: 1 ½ pages

Resources: what's in the book, the Win32 docs, and the developers

(note: probably need a code sample here)


Troubleshooting Application Manager

This is the big 'ol list of things that may go wrong when using Application Manager.

Projected size: ½ page

Artwork: none

Resources: already in the book


Creating a Help system

Introduction: Windows CE Help is an application that lets you display HTML-based Help files. The first part of this section is a general overview of Windows CE Help. It discusses how the user accesses Help files on a Windows CE mobile platform and how Windows CE Help interacts with the rest of Windows CE. The second part discusses how to create a Help system for Windows CE. The third part contains a full example of a Windows CE help file.

Projected size: 1 page

Artwork: none

Resources: the already-written chapter itself.


Understanding Windows CE Help

This is an overview of how CE Help works on a Windows CE device. This section is actually mostly technical UI, including which version of Help has what buttons and what they all do.

Projected size: 3 pages

Artwork: 2 screenshots of Help in action: one on the H/PC, and one on the Palm-size PC. (do we need callouts? Art question…)

Resources: it's already written and good to go


Software for Creating a Help System

This is a paragraph that lists the tools developers need before they start working on a Help file.


Creating a Help File

Contains general information regarding the HTML that


Using Jumps in a Help File

A paragraph showing how to create a jump


Using Graphics in a Help File

a paragraph showing how to insert a graphic, and suggestions for how to use graphics in a help file.

Artwork: none

projected size: 2 paragraphs.


Creating a Help Index


Creating Content for a Help System


Linking a Help Topic To an Application

Introduction: Once the developer has created a help file, they need to link it into their application. Here's how they do that.

Projected size: 1 paragraph

Art: none

Sources: already written in the chapter


Adding a topic to the All Topics List

The All Topics is a list of HTML links to all the available Help files on a Windows CE platform. The All Topics list for Windows CE Help versions 2.0 and 2.01 is a separate HTML file listing links to all the Help files on the platform. The manufacturer creates this file; do not alter it. Version 2.1 creates an All Topics list dynamically by selecting the name of the first topic in each Help file and a corresponding link (.lnk) file.

Projected size: 1 page

Artwork: none

Soruces: already written in the chapter


Creating Context-Sensitive Help

The most common way to call help in Windows CE Help versions 2.0 and 2.10 is from the ? button in the upper right-hand corner of the application screen or dialog box. The user accesses Help on version 2.01 by choosing Help in the Start menu. The following code example shows how to call Help from an application, using the CreateProcess function.

Projected size: 2 paragraphs

artwork: none

resources: already written in the chapter


Creating Pop-up Help

Pop-up help is considered separate from Windows CE Help, but it does basically the same thing (tells the User how to do something). Here's how to program it.

Projected size: 1 paragraph

artwork: none

resources: already written in the chapter


Working with Spell Checker

The Splusa.lib component spelling checker includes a read-only main dictionary, supports user-defined dictionaries, and suggests words for replacing misspelled words. This section discusses how to use the spell checker engine to increase the capabilities of your application.

Projected size: 4 pages

Artwork: (none currently): I'd probably like a screenshot or two of the spellchecker in action, though.

Resources: the already-written chapter itself.


Initializing the Spell Checker

Talks about how the developer sets up the Spell Checker engine, and what Windows CE does in specific.

Projected size: 1 page

Artwork: maybe 1 conceptual shot, but probably not.

Resources: the already-written chapter


Adding a Dictionary

Discusses how to add a dictionary to the spell checker during the initialization process.

Projected size: 1 page

Artwork: none

Resources: the already-written chapter.

(note: probably need a code sample for these)


Setting the Spelling Session Options

Discusses the options a developer can set while initializing the Spell Checker.

Projected size: 1 paragraph

Artwork: none, currently. May have one if the options (which I haven't looked into too much) creates any form of UI, but I don't think this is likely.

Resources: the already-written chapter itself.

(Note: probably need a code sample for these)


Using the Spell Checker

Discussion of how to call the Spell Checker engine to check spelling, and how to receive data. This section is an introduction to the next H3 sections, which contain most of the practical information.

Projected size: 1 paragraph

Resources: the already-written chapter

(note: these may not be necessary: you might be able to put them all under one heading.)


Sending Information to Spell Checker

How to send information to spell cheker.

Projected size: 1 paragraph

Resources: the already-written chapter


Recieving Suggestions from Spell Checker

How to make your application respond to information received from Spell Checker.

Projected Size: 2 paragraphs

Resources: the already-written chapter.

Artwork: none


Implementing Automatic Spell Checking

How to implement the Automatic Spell Checker.

Projected size: 1 paragraph

Resources: the already-written chapter

Artwork: none.


Ending the Spelling Session

Paragraph telling the developer how to end the Session.

Projected size: 1 paragraph

artwork: none

Resources: the already-written chapter


Integrating Windows CE Bundled Applications

Windows CE bundles a variety of applications for your use. These applications, while not part of the operating system, are often integral to any application's success on a given device. The following applications expose APIs you can use in developing your own applications.

  • Contacts Database
  • Windows CE Help
  • Input Method Editor
  • Spelling Checker.

This chapter discusses how to access the capabilities of the bundled applications, and how to integrate those APIs into your own applications.


Contacts Database

Contacts, a Windows CE personal information manager (PIM) application, is organized as a series of records, called address cards. Each address card contains a number of fields, called properties. Among the many predefined properties are name, address, telephone number, birthday, and anniversary. Contacts is bundled with Windows CE and includes a graphical user interface.

Contacts can be used as is, if the default application settings meet the needs of the user. However, you can take advantage of the open application programming interface and design new Contacts database applications. For example, you can design an application to track a doctor’s patient-contact information, including dates of previous and future office visits. You can retrieve, store, and query the records and properties in your Contacts-based database.

Because Contacts is built on the Windows CE object store, you need to be familiar with the object store to completely understand the database. For more information about the object store, see Accessing Persistent Storage.

A Contacts database includes the following basic elements:

  • Address card

    An address card stores information about one individual. The information is parsed into a set of predetermined properties. Each address card is represented by an AddressCard structure. The number of address cards in a Contacts database depends on the amount of available memory on the hardware device. To determine the number of records in the database, call the GetNumberOfAddressCards function.

  • Object identifier and position index

    A typical operation performed against an address card requires identification of the record and specification of its location in the database. To accomplish this, Windows CE assigns each address card a unique object identifier and a position index value. If you know the object identifier for an address card, you can get its position index by calling the GetAddressCardIndex function. Conversely, by calling GetAddressCardOid and passing in the position index as a variable, you can get the object identifier.

  • Property and property tag

    A property has an associated identifier called a property tag that is unique within the context of a single address card. To manipulate a property, you set the property tag with a call to the SetMask function. For example, you set the HHPR_HOME_TELEPHONE_NUMBER property tag to enable the home telephone number property of an address card. When accessing a property, you use the GetPropertyDataStruct function to specify whether the information should be retrieved by the position index, property name, or property tag. The information is then passed into a PropertyDataStruct structure.

  • Sort order

    Sort order specifies how the records are indexed. For example, records can be sorted according to the names or telephone numbers of the individuals. You can use up to four properties as the position indexes for sorting. However, you can sort only one property at a time. You specify an array of sort order options when you create a new database using CreateAddressBook and you specify a sort order out of the options list when you open an existing database using OpenAddressBook. You can change and retrieve the sort-order properties using the SetColumnProperties or GetColumnProperties function. In addition, you can change or retrieve the current sort order in an opened database using the SetSortOrder or GetSortOrder function.


Programming with the Contacts Database

Designing your database application involves calling the Contacts API to manipulate the database elements. In general, your application will include the following processes:

  • Opening your database
  • Adding and removing address cards
  • Opening address cards
  • Searching for a named property
  • Modifying address cards
  • Closing the opened address cards
  • Closing your database

You can use wrapper functions to map a user-defined setting to a system-defined one. For an application that tracks patient visits to a doctor, you can use the stBirthDate and stAnniversary properties to hold the time and dates of previous and future office visits. The following code example shows how to write a SetOfficeVisitDates function to set these two properties.

void SetOfficeVisitDates(SYSTEMTIME stVisit, BOOL fPrev, ADDRESSCARD * myAC)
{
   if(fPrev)
         myAC.stBirthdate=stVisit;      // previous office visit
   else
         myAC.stAnniversary=stVisit;    // future office visit
}

Similarly, the following code example shows how to write a GetOfficeVisitDates to return the appropriate date for the office visit.

SYSTEMTIME GetofficeVisitDate(BOOL fPrev, ADDRESSCARD myAC)
{
   if(fPrev)
        return myAC.stBirthdate;        // previous office visit
     else
        return myAC.stAnniversary;      //  future office visit
}


Opening the Contacts Database

Before you can work with any address cards, you must first open your database and specify a sort order and a handle to a window. The window receives notification messages from the object store. Before you can open the database, you must create it by calling the CreateAddressBook function.

To open the Contacts database

  1. Call the OpenAddressBook function.
  2. Define a sort order to be used and specify a window to receive the notification message.

    If the Boolean function returns TRUE, you can proceed to work with the opened database. The function returns FALSE if you attempt to open a non-existing database. If another Contacts database application has altered the sort order in an existing database, the function returns FALSE as well.

  3. Call GetLastError to get error information. If the error value is ERROR_FILE_NOT_FOUND, the database does not exist.
  4. Call the CreateAddressBook function to create the database before re-opening it.

    – Or –

    If the error value is ERROR_INVALID_PARAMETER, the database does exist, but the sort order has been altered. Call OpenAddressBook again, but without specifying a sort order.

The CreateAddressBook function can take as input a NULL array for the sort order properties. In this case, the function uses the surname, company name, office telephone number, and home telephone number properties as the default sort order parameters.

Multiple applications can open the Contacts database simultaneously. To coordinate data access and preserve data integrity, the operating system sends object-store notification messages to attached applications whenever the database is modified. The notification messages have the DB_CEOID_ prefix and are defined in the Windbase.h header file. After receiving a notification message, an application calls the RecountCards function to recalculate the number of records in the database.

The following code example shows how to open the Contacts database. When OpenAddressBook fails because the database does not exist, CreateAddressBook is invoked to create the database before OpenAddressBook is called again. The GetPropertyDataStruct function examines address card properties to ensure that the database has not been corrupted. The sample function returns TRUE if the database was successfully opened or FALSE if it was not.

BOOL OpenUpTheAddressBook(HWND hwndParent, HHPRTAG hhSortProp)
{
   DWORD dwError;                   // Error code
   HHPRTAG propList[MAX_COLUMNS];   // Sort-order property tags
   int nColumns;                    // Number of sortable columns
   int index;                       // Loop index

   if (!OpenAddressBook(hwndParent, hhSortProp)) {
      dwError = GetLastError();
      if (dwError == ERROR_FILE_NOT_FOUND)
      {

            if (!CreateAddressBook(NULL, 0))
               {
               goto InitError;
               }

            if (!OpenAddressBook(hwndParent, hhSortProp))
               {
               goto InitError;
               }

      } else if (dwError == ERROR_INVALID_PARAMETER)
      {
            if (!OpenAddressBook(hwndParent, 0))
               {
               goto InitError;
               }

            nColumns = MAX_COLUMNS;
            if (!GetColumnProperties(&propList, &nColumns))
               {
               goto InitError;
               }

            for (index = 0; index < nColumns; index++)
               {
               if (GetPropertyDataStruct(GPDS_PROPERTY,
                        propList[index], NULL) == GPDS_ERR)
                     {
                     MessageBox(hwndParent, IDS_CORRUPT_DATABASE,
                        MB_ICONEXCLAMATION | MB_OK);
                     goto InitError;
                     }
               }

            hhSortProp = propList[0];
            if (!OpenAddressBook(hwndParent, hhSortProp))
               {
               goto InitError;
               }
      } else
            {
            goto InitError;
            }
   }
      return TRUE;

   InitError:
      return FALSE;
}


Adding and Removing Address Cards

Follow these procedures for adding and deleting records from your Contacts database.

To add an address card to the Contacts database

  1. Initialize an AddressCard structure to hold whatever properties the new card is to have.
  2. For each property you will include in the new card, call the SetMask function on the initialized AddressCard with a (HHPR _*) property tag. Only properties that are enabled by SetMask can be saved to the object store.
  3. Call the AddAddressCard function to insert the new address card into the database.

If the operation is successful, AddAddressCard returns the object identifier in the myAC_oid parameter and the position index in the myAC_posIndex parameter. The position index is determined by the current sort order. The following code example shows how to add an address to the Contacts database.

ADDRESSCARD myAC;
PEGOID myAC_oid;
int myAC_posIndex;

memset(myAC,0,sizeof(myAC));   // to be sure everything starts from 0
myAC.stBirthday.wYear=1969;
myAC.stBirthday.wMonth=1;
myAc.stBirthday.wDay=19;
myAC.pszGivenName="Mimmo";
SetMask(&myAC, HHPR_BIRTHDAY);
SetMask(&myAC, HHPR_GIEVN_NAME);
if(!AddAddressCard(&myAC, &myAC_oid, &myAC_posIndex))
{
                               // error handling
}

To remove an address card from the Contacts database

  • Call the DeleteAddressCard function on the object identifier of the card.


Retrieving and Modifying Address Cards

Before you can modify an address card, you must first retrieve its current properties. There are two ways to retrieve the properties.

To retrieve an address card

  • Call GetAddressCardProperties to open the address card and retrieve a selected set of properties. This is the preferred method because this function allocates less memory.

    – Or –

    Call OpenAddressCard to allocate memory for an address card and retrieve all of the card’s properties.

To modify an address card

  1. Change members of the AddressCard structure retrieved by OpenAddressCard or GetAddressCardProperties.
  2. Set the property tag for each modified property with a call to the SetMask function to make the modification persistent.
  3. Call the ModifyAddressCard function on the modified AddressCard structure to complete the process.

To save system resources, you should free the memory allocated by OpenAddressCard or GetAddressCardProperties after the modification is finished by calling the FreeAddressCard function. For every call to OpenAddressCard or GetAddressCardProperties, there should be a corresponding call to FreeAddressCard.


Creating Help for Windows CE

Windows CE Help is an application that lets you display HTML-based Help files. The first part of this section is a general overview of Windows CE Help. It discusses how the user accesses Help files on a Windows CE mobile platform and how Windows CE Help interacts with the rest of Windows CE. The rest of the section discusses how to create a Help file for Windows CE.


Creating a Help System for Windows CE

The Help system for a Windows CE device is similar to a desktop Help system. This section discusses what tools you need to create a Help file for Windows CE, how to create the Help file, how to link a Help file into an application, and general pointers for designing a Help system.


Software Tools for Creating a Help System

A Help system for Windows CE consists of HTML files and graphics. Windows CE Help version 2.10 includes support for an All Topics list, using link (.lnk) files. You can also create an index. The following list describes the tools you should have to create a Help system:

  • An HTML editor, such as NotePad or FrontPage.
  • A remote screenshot program, such as CE Zoom.

    CE Zoom is a remote tool shipped with the Windows CE Toolkits. CE Zoom lets you take a screenshot from a connected Windows CE device.

  • An HTML and link validator.


Adding to the All Topics List

The All Topics is a list of HTML links to all the available Help files on a Windows CE platform. The All Topics list for Windows CE Help versions 2.0 and 2.01 is a separate HTML file listing links to all the Help files on the platform. The manufacturer creates this file; do not alter it. Version 2.1 creates an All Topics list dynamically by selecting the name of the first topic in each Help file and a corresponding link (.lnk) file.

To add a topic to the All Topics list for version 2.10

  1. Name the first topic in each Help file Main_Contents.

    Place this name only in files you want to include in the table of contents.

  2. Include “Main_Contents” in the <META> tag for each of these files.

    The following code example shows how to include a topic in the dynamic table of contents.

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<HTML>
<HEAD>
<META HTTP-EQUIV="Htm-Help" CONTENT="soltr.htm#Main_Contents">
<TITLE>Solitaire Help</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000">
<!-- PegHelp -->
<P><A NAME="Main_Contents"></A><B>Solitaire Help</B></P>
<A HREF="soltr.htm#playing_the_game">Playing the Game</A><BR>
<A HREF="soltr.htm#setting_solitaire_options">Setting Solitaire Options</A><BR>
<A HREF="soltr.htm#solitaire_scoring_systems">Solitaire Scoring Systems</A><BR>
<BR CLEAR=ALL>
<!-- PegHelp --><HR>
<!-- ************************Topic Break************************* -->
  3. Create a link file for each Help file in the table of contents.

    Create a link file with NotePad or another ASCII editor. Name the link file as you want Help to display the link in the table of contents. For example, the Solitaire Help file is Soltr.htm and the link file is Solitaire.lnk. The link in the table of contents is Solitaire. You can place spaces in the link file name. For example, the link file for Pocket Word is Pocket Word.lnk.

    The text in the link file tells Windows CE Help where to aim the link in the table of contents. The following code example is one entry in the Solitaire.lnk file.

    18#\windows\soltr.htm

    The number 18 in this example represents the number of characters in \Windows\Soltr.htm

    During program installation, install your link file in the \Windows\Help directory. You can install the Help file anywhere on the platform.


Creating Context-Sensitive Help

The most common way to call help in Windows CE Help versions 2.0 and 2.10 is from the ? button in the upper right-hand corner of the application screen or dialog box. The user accesses Help on version 2.01 by choosing Help in the Start menu. The following code example shows how to call Help from an application, using the CreateProcess function.

CreateProcess(TEXT,"peghelp.exe", TEXT("file:inkwrite.htm#font_and_ink"), NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL;

Pass the “peghelp.exe” string in as the first parameter, using the TEXT macro first to convert the string to Unicode. Use TEXT on the second argument, this time passing a string containing the file name of the contents or specific Help topic you want to display. Set all other parameters to NULL, FALSE, or 0, respectively.


Creating Pop-up Help

Many localizers use pop-up Help to provide complete names or information when the UI does not provide enough display area. To access pop-up Help, a user selects and holds on the title of an item in the UI. Windows CE either provides more information or displays the message “No help provided.” Create a pop-up Help topic in the same manner as a Windows CE ToolTip. The following code example shows how to declare a pop-up Help topic in a resource file for a Palm-size PC.

CONTROL     "Static text~~This is a pop-up Help topic\rIt is also a
            ToolTip",IDC_STATIC,"TTSTATIC",WS_VISIBLE,19,24,56,8


Working with the Input Method Editor

Similar to a spelling checker or handwriting recognition engine, an Input Method Editor (IME) in Windows CE simplifies the process of providing input for users. As a user enters keystrokes, an IME monitors the keystrokes, anticipates which character the user intends to compose, and presents a list of candidate characters. From this list, a user can select the intended character while the IME converts the user's keystrokes into this final selected character. This method of input saves users the burden of having to compose the entire character.

IMEs are particularly useful in Asian languages where each language is typically comprised of thousands of characters and it is difficult to display every character on a keyboard. In addition, characters in Asian languages are often composed of multiple strokes and are very time-consuming to enter.

An IME provides windows through which users can enter keystrokes, view candidates, and select characters. Windows CE applications can use the functions and messages of the input method manager (IMM) to create and manage their own IME windows.


IME User Interface

The IME user interface consists of the status, composition, and candidate windows. By default, the system automatically creates and manages these windows for all windows that require text input. For many applications, this default processing is sufficient. An application that relies entirely on the system for IME support is considered IME-unaware because it is unaware of the many system tasks associated with managing IME windows.

In contrast, an IME-aware application participates in the creation and management of IME windows. Such an application controls the operation, position, and appearance of the default windows by sending, intercepting, and processing messages intended for IME windows. In some cases, an application can create and customize its own IME windows and provide complete processing for these windows.


IME Status Window

The status window provides information on the status of the IME (whether active or inactive) and allows the user to set the IME conversion mode.

To quickly change the conversion mode of the IME or to switch to another IME, users can use hot keys. To retrieve or set the value of an IME hot key, an application can call the ImmGetHotKey or ImmSetHotKey functions.

An application cannot add hot keys to the system however, it can initiate the same action as a hot key by using the ImmSimulateHotKey function.


IME Composition Window

Users compose characters in the composition window. Depending on the conversion mode, the composition window displays either converted or unconverted text. While composing characters, users can select a character in this window to display matching complete characters (candidate characters) in the candidate window.

An application can call the ImmGetCompositionWindow function to retrieve information about the composition window contained in the COMPOSITIONFORM structure, or ImmGetCompositionFont to get the logical font currently used to display characters in the composition window.

IME Composition String

The IME composition string is the current text in the composition window. Each composition string consists of one or more clauses, where a clause is the smallest combination of characters that the IME can convert to a final character.

An application can register a string into the IME dictionary by calling the ImmRegisterWord function. Calling ImmUnregisterWord removes a string from the IME dictionary.

As users enter text in the composition window, the IME tracks the composition status, which includes attribute information, clause information, typing information, and cursor position. Use the ImmGetCompositionString and ImmSetCompositionString functions to retrieve and set the composition status, or to retrieve and set the characters, attributes, and clauses of the composition string. ImmSetCompositionString also allows notification messages to be sent to the composition window, to ensure that this window updates its appearance based on the changes specified in this call. Applications that set a combination of composition status elements typically set the fNotify parameter to FALSE for all but the last call to this function so that only one notification message is generated for the composition window.

The attribute information is an array of 8-bit values that specifies the status of characters in the composition string. In this array, all characters of one clause must have the same attribute. Each byte in the string has one value, including one byte for each of the lead and second bytes of any double-byte characters in the string.

The clause information is an array of 32-bit values that specifies the positions of the clauses in the composition string. Each clause has one value in the array that specifies the beginning of the clause. These clause positions are followed by a final value that specifies the length of the full string. Each value in the array specifies the offset, in bytes, from the beginning of the composition string to the clause. The first value is always 0 because the first clause always starts at the beginning of the string. For example, if a string has two clauses, the clause information has three values: the first value is 0, the second value is the offset of the second clause, and the third value is the length of the string.

The typing information is a null-terminated character string that represents the characters entered at the keyboard.

The cursor position is a value indicating the position of the cursor relative to the characters in the composition string. This value is the offset, in bytes, from the beginning of the string. If this value is 0, the cursor is immediately before the first character in the string. If the value is equal to the length of the string, the cursor is immediately after the last character. If the value is -1, the cursor is not present.


IME Candidate Window

The candidate window contains a list of candidate characters for the selected character in the composition window. Users can scroll through this list and select the desired character before composition of the character is completed in the composition window. The user can compose the desired text in this manner until the composition string is finalized and the candidate window is closed.

Once the desired character has been determined, the IME sends this character to the application in the form of WM_IME_CHAR or WM_IME_COMPOSITION/GCS_RESULT messages. If the application does not process these messages, the DefWindowProc function translates them into one or more WM_CHAR messages.

The characters in the candidate list are arranged in an array of strings in the CANDIDATELIST structure. Use the ImmGetCandidateList function to retrieve a candidate list and copy the list to a buffer. Use the ImmGetCandidateListCount function to retrieve the total size, in bytes, of all the candidate lists. To get or set information about the candidate list window in the CANDIDATEFORM structure, use the ImmGetCandidateWindow and ImmSetCandidateWindow functions.


IME Window Class

The IME window class is a predefined global system class that defines the appearance and behavior of the standard IME windows. This class is similar to common control classes with which you use the CreateWindowEx function to create a window. Like static controls, an IME window does not respond to user input by itself but, rather, notifies the IME of user input actions. To carry out a response to user action, an IME window processes messages sent by the IME or the application.

IME-aware applications can create their own IME windows using the IME window class. By using the IME window class, applications can take advantage of the default processing of the IME window while retaining control of the window's positioning.


IME Window Messages

The system sends IME window messages to the window procedure of an application when certain events occur that affect an IME window. For example, the system sends the WM_IME_SETCONTEXT message to an application when a window is activated. IME-unaware applications pass these messages to the DefWindowProc function, which sends them to the corresponding default IME window. IME-aware applications either process these messages or forward them to their own IME windows.

To have an IME window carry out an action, use the WM_IME_CONTROL message.

To have the IME notify an application about changes to the composition string, use the WM_IME_COMPOSITION message. Use the WM_IME_NOTIFY message for general changes to the status of IME windows.

To process the WM_IME_COMPOSITION message, applications test the bits in the lParam parameter and call the ImmGetCompositionString function to retrieve the indicated string or data. The following code sample checks for the result string, allocates sufficient memory for the string, and retrieves the result string from the IME.

HIMC hIMC;
HWND hWnd;
DWORD dwSize;
HGLOBAL hstr;
LPSTR lpstr;

case WM_IME_COMPOSITION:
    if (lParam & GCS_RESULTSTR)
    {
        hIMC = ImmGetContext(hWnd);

        If (!hIMC)
            MyError(ERROR_NULLCONTEXT);

        // Get the size of the result string.
        dwSize = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0);

        // Increase buffer size for NULL terminator,
        //   maybe it is in UNICODE.
        dwSize += sizeof(WCHAR);

        lpstr = (LPSTR) LocalAlloc(LPTR, dwSize);
        if (lpstr == NULL)
             MyError(ERROR_GLOBALLOCK);

        // Get the result strings that is generated by IME into lpstr.
        ImmGetCompositionString(hIMC, GCS_RESULTSTR, lpstr, dwSize);
        ImmReleaseContext(hWnd, hIMC);

        // Add this string into text buffer of application.

        LocalFree(lpstr);
    }


Input Context

IME windows use an input context to retrieve information about the status of the IME. An input context is an internal structure maintained by the IME. By default, the system creates and assigns an input context to each thread. Within the thread, this default input context is a shared resource and is associated with each newly created window. Input contexts require considerable system resources, so use them conservatively.

To retrieve or set information in the IME, an application must first obtain a handle to the input context associated with a specified window. To obtain this handle, use the ImmGetContext function. You can use the returned handle in subsequent calls to the IMM functions to retrieve and set IME values, such as the composition window style, the composition style, and the status window position. The ImmNotifyIME function notifies the IME about changes to the input context. Once finished with the context, you must release it using the ImmReleaseContext function.

Since the default input context is a shared resource, any changes you make to it apply to all windows in the thread. However, you can override this default behavior by creating and associating your own input context to one or more windows in the thread. The changes you make to your own input context apply only to the windows with which it is associated.

To create an input context, use the ImmCreateContext function. To assign an input context to a window, use the ImmAssociateContext function. ImmAssociateContext returns the handle of the previously associated input context. If you have not previously associated an input context with the window, the returned handle is for the default input context. If you want to use your own input context at a later time, you should maintain this default input context handle and later associate your input context with the window.

To create a new component that is a member of an input context structure, use the ImmCreateIMCC function. This function returns an input context component handle and initializes the component with zeros. To retrieve or change the size of the input context component, use the ImmGetIMCCSize and ImmReSizeIMCC functions. An application that creates an input context component must call the ImmDestroyIMCC function to free the component before the application terminates.

To obtain a handle to an input context, an IME can call ImmLockIMC. To obtain a handle to an input context component that is a member of an input context, an IME can call ImmLockIMCC. With each call to these functions, the total number of handles (or lock count) is incremented for the corresponding input context or input context component. To get the lock counts of an input context or input context component, use the ImmGetIMCLockCount and ImmGetIMCCLockCount functions. To release a handle and decrement the lock count, use ImmUnlockIMC or ImmUnlockIMCC.

Once an input context is associated with a window, the system automatically selects that context when the window is activated and receives the input focus. The style and other information in the input context affects subsequent keyboard input for that window, determining the appearance and operation of the IME.

You must destroy any input context you create before terminating your application, but before destroying the input context, you must disassociate it from any windows in the thread. Use the ImmAssociateContext function to disassociate the input context, and the ImmDestroyContext function to destroy it.


Working with the Spelling Checker

The Splusa.lib component spelling checker includes a read-only main dictionary, supports user-defined dictionaries, and suggests words for replacing misspelled words.


Initializing a Spelling Session

An application calls the SplInit function to initialize a spelling checker session. SplInit does the following tasks:

  • Creates a handle for a spelling checker session
  • Loads the main dictionary
  • Creates internal user dictionaries
  • Loads external user dictionaries

Each call that an application makes to the spelling checker specifies a pointer to the handle of the spelling checker session. The handle associates a call to a particular spelling checker session.


Spelling Dictionaries

The SplInit function loads one main dictionary, creates three internal user dictionaries, and loads as many external user dictionaries as an application specifies for the spelling checker session.

SplInit loads a read-only main dictionary for each spelling checker session. Currently, Windows CE does not support replacing this main dictionary with a dictionary supplied by an application, but it can specify additional dictionaries to load when it calls the spelling checker.

In addition to loading the main dictionary, SplInit creates three internal user dictionaries for each spelling checker session. These dictionaries are empty when a spelling checker session is initialized. During a spelling checker session, an application can add to and modify these internal user dictionaries. When an application ends a spelling checker session with a call to SplQuit, the internal user dictionaries are not saved. The following table describes the internal user dictionaries:

Internal user dictionary
Description
SPL_IGNORE_LEX | Contains words that the spelling checker finds, but that the user does not want to change. The spelling checker ignores words in this list for the remainder of the spelling checker session.
SPL_CHANGE_ALWAYS_LEX | Contains words that the spelling checker finds, paired with words that replace subsequent occurrences of the found words.
SPL_CHANGE_ONCE | Contains words that the spelling checker finds and the user changes. The spelling checker continues to find subsequent occurrences of the words.

An application can specify external user dictionaries for SplInit to load at the beginning of a spelling checker session. The ppwsz parameter is a pointer to a list of external user dictionaries consisting of a path and a file name for each dictionary. During a spelling checker session, an application can modify external user dictionaries. When the spelling checker session ends, the spelling checker writes external user dictionaries using the specified path and file name.

The internal and external user dictionaries are lists of NULL-terminated words. Two NULL characters indicates the end of a dictionary. In the SPL_CHANGE_ALWAYS_LEX and SPL_CHANGE_ONCE internal dictionaries, the words are listed with the incorrect word first and the correct word second. For example:

…thier\0their\0wierd\0weird\0\0

The functions that an application can use during a spelling checker session to modify internal and external user dictionaries are described in the following table:

Function
Description
SplAddUserDict | Adds a single word or word pair to the specified dictionary
SplRemUserDict | Deletes a single word or word pair from the specified dictionary
SplClrUserDict | Clears all entries from the specified dictionary

The SplEnumUserDictionary function enumerates the contents of a specified internal or external user dictionary. The function writes the dictionary contents to the output buffer specified in the SPLBUFFER structure. One use of enumeration is to provide dictionary entries to an application, which displays the entries for a user to edit.


Spelling Session Options

After initializing a spelling checker session, an application can call the SplOptionSet function to customize the spelling checker’s operations. Call the SplOptionGet function to retrieve current settings. For example, call SplOptionGet so that an application can display current settings to a user.


Ending Spelling Sessions

Calling the SplQuit function ends a spelling checker session by doing the following tasks:

  • Invalidating the spelling checker session handle created when an application called the SplInit function to initialize the spelling checker session.
  • Freeing all resources that the SplInit function allocated.
  • Deleting internal user dictionaries.
  • Saving external user dictionaries to the files specified with the SplInit function.


Finding Spelling Mistakes

To use the spelling checker, an application fills a SPLBUFFER structure with the information the spelling checker needs. This information includes the location of the beginning of the input buffer, which contains the text to be processed, and the location of the output buffer where the speller checker returns suggestions for misspelled words.

After initializing a spelling checker session, an application calls the SplCheck function. With this call, the spelling checker processes the text in the input buffer. When the spelling checker finds an error, it identifies the location of the error relative to the beginning of the buffer and sets an error flag that indicates what the error is. The spelling checker updates the SPLBUFFER structure with this information. An application updates SPLBUFFER by adjusting the beginning of the input buffer and setting a processing mode flag that indicates how the spelling checker proceeds.


Getting Suggestions

Call the SplSuggest function to receive suggestions for alternatives to misspelled words from the spelling checker. To get suggestion scores, an application must call the SplOptionSet function and set the SPL_SCORE_SUGGESTIONS flag.

A call to SplSuggest causes the spelling checker to return up to eight alternative words to the output buffer specified in the SPLBUFFER structure. The aspl member of SPLBUFFER is an array of SPLSUGGEST structures, one structure for each word. The first member of this structure points to a word in the output buffer, and the second member specifies a score for the suggested alternative. The score indicates the extent to which the spelling checker changed the misspelled word to produce the suggested replacement for the word. An application can use suggestion scores to exclude some words from the list of alternatives that it displays to a user.


Automatic Spell Checking

An application can implement an automatic spelling checker that corrects words as a user types. A single call to the SplReplace function checks a word and if it is wrong, returns to the SPLBUFFER structure a suggested replacement for the word. However, SplReplace returns a suggestion only if the error tolerance of the suggestion is within the limit set by an application with a call to the SplLimitSet function. With the error tolerance, an application can limit automatic replacements to obvious mistakes, such as


Integrating the Inbox

This chapter discusses how to integrate and develop the Inbox application.


Integrating Pocket Inbox

This section discusses how to send and receive e-mail using the Pocket Inbox application. The first section will contain a general overview of how the application works, tracing a mail structure as it goes through the program. The second part will contain all the technical information for each of the steps that were discussed in the overview.

Projected size: ½ page

Resources: The current chapter, as well as the Jupiter developers.


Understanding Pocket Inbox

An overview of how the Inbox application creates, sends, and receives a piece of mail. This section introduces the developer to how Pocket Inbox is put together, as well as showing how all the different sections modify a piece of mail.

Artwork: probably a couple of screenshots showing different parts of the application. Will also need a flow chart showing how a mail message moves through the application.

Projected size: 4 pages

Resources: The book and the Jupiter developers, as well as the normal Outlook SDK.


Working With a Mail Message

This section discusses how to


Creating a Mail Message

Tells how to create a mail message

Projected size: ½ page

Artwork: none

Resources: already written

(Note: probably need code sample)


Moving and Copying a Mail Message

Tells how to move and copy a mail message

Projected size: ½ page

Resources: already written + Jupiter developers

(note: probably need code sample)


Attaching a File To a Mail Message

Tells how to attach a file to a Mail Message

Projected size: 1 page

Resources: already written + Jupiter developers

(note: probably need code sample)


Retrieving a Mail Message

Tells how to get a mail message off of a server

Projected size: 1 page

Resources: already written + Jupiter developers

(note: probably need code sample)


Sending a Mail Message

Tells how to send a mail message from a device to a server

Projected size: ½ page

Resources: already written + Jupiter developers

(note: probably need code sample)


Updating the Message Store

Tells how to update the message store and why you should.

Projected size: ½ page

Resources: already written + Jupiter developers

(note: probably need code sample)


Handling a Mail Error

Tells how to get information regarding mail errors.

Projected size: ½ page

Resources: already written + Jupiter developers

(note: probably need code sample)


Managing Memory for Mail Applications

Tells how to manage memory with mail applications

Projected size: 1 page

Resources: already written + Jupiter developers

(note: probably need code sample)


Developing a Mail Transport

This section discusses how to build a mail transport. It begins with a general overview of how a mail transport works in relation to Windows CE, the Inbox app, the server and the user. Then, it goes into how to develop one. It ends with a discussion of the tools necessary to build a Mail Transport.

Projected size: ½ page

Artwork: none

Resources: The book and the Jupiter dev team.


Understanding a Mail Transport

Discussion of how a mail Transport interacts with Pocket Inbox, your own application, and whatever it is out there that the Transport connects to (usually a server, but it could be something else…)

Projected size: 2 pages

Artwork: probably will need a conceptual drawing showing how the Mail Transport fits with Windows CE and an application.

Resources: the Jupiter dev team.


Developing a Mail Transport

This is the sketchiest part of the section I have: the developers said there was information here, but they haven't given me any. So, I need to set up interviews and whatnot.

Projected size: 4 pages (?)

Artwork: I have no clue

Resources: the Jupiter dev team


Registering a Transport Service

This is where the general information for registering a mail transport goes. All the other subheadings are for specific instances of interacting with the Inbox.

Projected size: 1 page

Artwork: none

Resources: already written, plus the Jupiter dev team.


Registering a Transport Service Library

This section shows how to register a Transport library with Inbox, from both the technical and end-user side.

Projected size: 2 pages

Artwork: none

Resources: already written


Registry Entries Used by Inbox and the Transport Service

This shows the registry entries used by Inbox in regards to the Transport Service. Basically, it's a Reference entry.

Projected size: 3 pages.

Artwork: none

Resources: already written.


Debugging and Troubleshooting

An effective set of debugging tools can speed up the time-consuming process of debugging an application and make it easier to determine the source of problems. The Windows CE IDE provides a set of tools for such tasks as emulation and remote debugging on H/PCs, and special tools for debugging other Windows CE-based platforms.


Desktop Emulation Debugging and Testing

The desktop emulation tool included with Windows CE allows you to write and debug an H/PC application on any computer running Windows NT. With emulation, you can test and debug your application without downloading your software to a device. You can even use emulation to write a Windows CE-based application without having a device. Though you can determine a great deal about the functionality and appearance of your application with emulation, once your application is running correctly in the emulation environment, you must download it to a target device for final testing and evaluation. In addition to testing for defects and reliability, testing for usability is critical, because most Windows CE-based platforms have a small desktop area that can present access problems.


Remote Debugging and Testing

In addition to desktop emulation, Windows CE contains several tools that enable you to examine your application as it runs. The following table shows these remote tools.

Tool
Purpose
Remote Process Viewer | Displays a graphical view, on the desktop computer, of Windows CE processes and thread characteristics. The Process Viewer enables you to view memory usage and search for memory leaks.
Remote File Viewer | Displays a hierarchical view, on the desktop computer, of the Windows CE object store. The File Viewer enables you to transfer objects between the emulation object store and the device.
Remote Zoomin | Captures an image from the remote device and displays it on the desktop computer.
Remote Registry Editor | Manages remote device registry entries from the desktop computer.
Remote Heap Walker | Displays information about heap identifiers and flags for processes that are running on a remote device. The Heap Walker is a useful tool to determine if an application has memory leaks.
Remote Spy++ | Displays a graphical view, on the desktop computer, of window messages running on the remote device.

In order to use the remote tools, you must connect your desktop computer to your remote device with a standard serial cable connection. For information on how to physically connect your desktop computer and a remote device, see the user’s guide that came with your device.


Non-Standard Debugging and Testing

If you are developing software for a Windows CE-based platform, such as an H/PC, you can use the Windows CE emulation environment and remote tools to debug and test your application. However, if you are developing applications for other Windows CE-based platforms, such as embedded systems, you will have to devise your own debugging and testing tools.

The Windows CE API includes the interfaces necessary to create a full-featured debugger application, such as the one provided with Visual C++ or WinDbg. Although the creation tools exist, the limited size of the Windows CE environment makes such a debugger unrealistic. If you decide to write a debugger application, the best approach is to create a debug client using Windows CE debug functions and to communicate the relevant events to a desktop computer-based debugger. When writing a debugging application, choose one of the following ways to start the debugging session:

  • Launch a process with CreateProcess and specify DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS in the dwCreateFlags member. In addition to the DEBUG_PROCESS flag, use CREATE_SUSPENDED to prevent the application from running after it is initialized. Once suspended, a debugger can initialize and add any appropriate break points.

    – Or –

  • Attach to an already running application. To attach to a process, you must obtain permission to access the process by calling OpenProcess and passing in the identifier of the process you want to debug. If a valid handle is returned, call DebugActiveProcess to start debugging. After attaching to the process, the CREATE_PROCESS_DEBUG_EVENT returns the primary thread handle and the multiple CREATE_THREAD_DEBUG_EVENTs return the secondary thread handles. After attaching to the process and receiving the initial events, the process’s thread is resumed. Unlike Windows NT, Windows CE has no EXCEPTION_DEBUG_EVENT. All thread handles must be closed with CloseHandle when you stop debugging.

Windows CE has built-in support for just-in-time (JIT) debugging. JIT debugging enables you to run an application outside of the development environment. When an error occurs, the application calls the installed debugger. You register your JIT debugger by placing the name of your debugger in the registry located at HKEY_LOCAL_MACHINE\Debug. To enable JIT, you must warm-boot the device after the JITDebugger value is added to the registry.

If you choose not to write a debugging application, you must create some debugging tools inside your Windows CE-based application. Windows CE provides both functions and structures to do this. For a list of supported debugging functions, see Lists of Functions and Interfaces.

No matter what tools you use, you must thoroughly test your application on every kind of device that will run your application.


Windows CE Emulation

The Microsoft® Windows® CE emulation environment provides a Microsoft® Windows® NT desktop version of Windows CE. This environment allows you to run your applications on a simulated version of your targeted Windows CE platform. The emulation environment supports Windows CE common controls and the Windows CE object store file system.

The object store is managed by a file called FILESYS.EXE and contains the Windows CE registry, databases, and other files. The tree structure is identical to the tree structure of the object store on the remote device. Most of the Windows CE functionality is retained, including desktop shortcuts, object store browsing, drag-and-drop operations, context-sensitive menus, and support for all shell APIs.

To run emulation on your desktop PC, you must first have Microsoft® Windows NT® 4.0, Microsoft® Visual C++® 6.0, and the Microsoft® Windows® CE Toolkit for Visual C++® 6.0 installed. Each Windows CE platform has slightly different emulation procedures. To learn the emulation procedures for your targeted Windows CE platform, see the emulation section in the platform SDK documentation.


Starting Emulation

To Start Emulation

  1. Click Start, and point to Programs.
  2. Point to Windows CE Platform SDK.
  3. Click on the emulation option that corresponds to the platform you wish to emulate. For example, to emulate the Handheld PC, select Desktop HPC Emulation.

The emulation window is used to observe the behavior of Windows CE applications under development without having to download executables or have a remote device connected to your desktop PC. The emulation window is a simulation of the desktop environment on the remote device. For example, the following picture illustrates an emulated H/PC.


Closing Emulation

To Close Emulation

  • In general, close the emulation environment the same way that you would close the target platform it is emulating. For example, to close the H/PC emulation, click the Start button on the simulated H/PC, then click Suspend.


Building Applications for Emulation

To build an application for emulation, link your project to the following libraries:

  • Coredll.lib
  • Msvcrt.lib

The functions in these libraries are declared in Windows.h. To add messaging functionality to your application, include Msgstore.h. To add e-mail functionality, include Addrstor.h.

Note Check the SDK documentation of your Windows CE target platform to determine what other libraries your project should be linked to for emulation.

To build an application for emulation

  1. From the Microsoft® Visual Studio™ IDE, on the File menu, click New.
  2. In the New dialog box, click the Projects tab, and select Win32 Application.
  3. In the Platforms check box , select Win32 (WCE x86 em).
  4. In the New dialog box, type a text string in the Project Name field and click OK.
  5. After you build and link the executable file, Visual Studio will display the Copying Executable Path And File Name dialog box. Enter the destination location for your executable file, and then click OK.

Note This dialog box displays only when you build and link a new executable file. When you rebuild an application, use the Remote Object Viewer to copy and move the executable file to a different folder in the simulated file system, or use the following procedure.

To move the executable file of a rebuilt application

  1. On the Project menu, click Settings.
  2. In the Project Settings dialog box, click the Debug tab.
  3. Clear the Remote Executable Path and File Name field and click OK. This will cause Visual Studio to display the Copying Executable Path And File Name dialog box during the next build. You can also move the executable file by changing the file path that appears in the Remote Executable Path and File Name field and clicking OK.

To link a library file to your application

  1. Load your project workspace in Visual Studio.
  2. On the Projects menu, click Settings.
  3. In the Project Settings dialog box, click the Link tab.
  4. If the library file is not linked to your project, type the library file name in the Object/library modules field and click OK.

To implicitly link a DLL to your application

  1. Load your project workspace in Visual Studio.
  2. On the Tools menu, click Options.
  3. In the Options dialog box, click the Directories tab.
  4. In the Show directories for drop-down box, choose the correct file type.
  5. Make sure the drive letters and paths to the directories containing the DLLs you want to link to your project are correctly listed in the Directories field.
  6. Click OK, then on the Project menu, click Settings.
  7. In the Project Settings dialog box, click the Link tab.
  8. Make sure the names of the DLLs you want to link to your project are correctly listed in the Object/library modules field.
  9. Click OK.


Running Applications Under Emulation

There are two ways to run an application under emulation:

To run an application under emulation

  • From within the Visual Studio IDE, click either the Execute or Go button on the toolbar, or click Execute on the Build menu. This will start EXPLOREM.EXE and FILESYS.EXE to execute your application within the emulation window

    – Or –

    From within the emulation environment, start an application the same way you would on the device being emulated. For example, on the H/PC, click the Start button, click Run, enter the path for your executable file, then click OK.

Note The default display area for the emulation window is the upper-left corner of your screen.

The emulation environment mimics the memory capacity of the device being emulated and displays appropriate error messages when memory limits are exceeded. Be aware that some applications run faster within emulation than they will on your target device. This is partly due to message handling on desktop machines.

Even though the message handling procedures of the target device are emulated, some subtle differences exist. Use emulation through the final stages of development, but always test applications on the remote device before releasing the application.


Changing the Size of the Emulation Window

If you want to change the display size of the emulation environment window, you'll have to run the Windows NT registry editor (regedit.exe).

To change the size of the emulation window

  1. If you are running the emulation, close it by clicking the emulation environment Start button, then click Suspend.
  2. Click Start, and point to Run.
  3. In the Open field, type "Regedit" and click OK.
  4. In the Windows Registry editor, click HKEY_LOCAL_MACHINE.
  5. Click SOFTWARE.
  6. Click Microsoft.
  7. Click Windows CE Platform SDK.
  8. Click Emulation.
  9. Click the entry that corresponds to your target device. For instance, click H-PC if your target device is an H/PC.
  10. Click Screen Settings.
  11. Modify the SM_CXSCREEN and SMCYSCREEN values and close the registry editor.
  12. Start emulation.


Troubleshooting Emulation

When I build I get connection errors. What’s wrong?

You may have the wrong environment selected. Check the default project workspace, and make sure it is set to one of the emulation options.

I rebuilt my .exe and the IDE never gave me the option of where to put my executable. What’s wrong?

You may have to move your executable file to the correct directory.


Preface

The Microsoft Windows CE User Interface Services Programmer’s Guide in conjunction with the Windows CE Programmer’s Guide, the Windows CE Communications Guide, and the Windows CE SDK Library provides all the information you need to write applications for devices based on the Microsoft Windows CE operating system.

The Windows CE User Interface Services Guide contains the following chapters:

Introduction to User Interface Services on Windows CE

This chapter provides an overview of the Graphics, Windowing, and Events Subsystem (GWES) module, and specifically, Windows CE User Interface Services. It explains the specific components included in GWES and how those components work together to provide window management and event handling, power management features, control, menu, GDI, dialog box, resource, and user input support.

Working with Windows and Messages

This chapter discusses Windows CE windowing and messaging features. Emphasis is placed on creating, sizing, positioning, and destroying windows as well as sending and responding to messages.

Using Resources

This chapter provides information about resources such as dialog and message boxes, menus, icons, carats, bitmaps, and timers.

Creating Controls

This chapter explains how to add standard controls to a user interface.

Working with Graphics

This chapter provides an overview of the Windows CE Graphics Device Interface (GDI). It also explains how to work with graphics objects such as pens, brushes, shapes, fonts, and lines.

Receiving User Input

This chapter discusses various user input methods supported by Windows CE, such as keyboard, mouse, handwriting, and speech API (SAPI). It also describes how to use virtual keys.

Designing a User Interface for Windows CE

This chapter provides a general overview of user interface design concepts and presents specific suggestions for working with windows and dialog boxes, adding controls, creating icons, using color, and layout.

Appendix A: Window and Control Styles

This appendix lists window and control styles supported by Windows CE.


About the Code Samples Included in this Guide

The Windows CE User Interface Services Programmer’s Guide includes code samples developed with Microsoft® Visual C++® version 6.0 and the Microsoft® Windows® CE Toolkit for Visual C++® version 6.0. Concepts presented in sample applications are ported for a Handheld PC (H/PC) and for an H/PC running Microsoft® Windows® CE, Handheld PC Professional Edition software, but apply to all Windows CE-based platforms.


Document Conventions

The following table shows the typographical conventions used throughout this book.

Convention
Description
monospace Indicates source code, structure syntax, examples, user input, and application output. For example,

ptbl->SortTable(pSort, TBL_BATCH);
Bold | Indicates an interface, method, function, structure, macro, or other keyword in Windows CE, the Microsoft Windows operating system, C, or C++. For example, CommandBar_Height is a function. Within discussions of syntax, bold type indicates text must be entered exactly as shown.
Italic | Indicates placeholders, most often method or function parameters; these placeholders stand for information that must be supplied by the implementation or the user. For example, lpButtons is a function parameter. Also indicates new terms defined in the glossary.
UPPERCASE | Indicates flags, return values, messages, and properties. For example, WSAEFAULT is a Windows Sockets error value, MF_CHECKED is a flag, and TB_ADDBUTTONS is a message. In addition, uppercase letters indicate segment names, registers, and terms used at the operating-system command level.
( ) Indicate one or more parameters that you pass to a function, in syntax.


Introduction to User Interface Services on Windows CE

Windows CE combines the Microsoft® Win32® User Interface (UI) and Graphics Device Interface (GDI) libraries into the Graphics, Windowing, and Events Subsystem module (Gwes.exe). GWES is the interface between the user, your application, and the operating system.

GWES supports all the windows, dialog boxes, controls, menus, and resources that make up the Windows CE user interface. This interface enables users to control applications. GWES also provides information to the user in the form of bitmaps, carets, cursors, text, and icons.

Even Windows CE-based platforms that lack a graphical user interface use GWES basic windowing and messaging capabilities and power management functions.


GWES Component Model

Because Windows CE is a modular OS, an original equipment manufacturer (OEM) can design a platform-specific OS by selecting from a set of available software modules. An OEM can further customize an OS by selecting from a subset of available components for a particular module. Windows CE supplies several pre-tested component configurations that can be divided into three general categories: minimally-featured configurations, moderately-featured configurations, and full-featured configurations.

Minimally-featured configurations build a basic version of Windows CE that includes the core OS or kernel (Core.dll) and selected GWES support, such as messaging, user input, and power management. Minimally-featured configurations do not display a user interface (UI) or contain window management features.

Moderately-featured Windows CE configurations include the core OS as well as support for the following GWES features:

  • Messaging and user input
  • Power management
  • Notification LED
  • GDI, including Microsoft® TrueType® and raster fonts, text drawing, palette, and printing
  • Customizable touch and calibration UI
  • Network UI dialogs
  • Wave API manager
  • Input method manager
  • Window and dialog management
  • Customizable UI

Some minimally-featured configurations also provide console, notification, and common control support.

Full-featured configurations build a full version of Windows CE, including all GWES component support. A complete listing of the components contained in full-featured configurations can be found in the Windows CE SDK Library.


Window Management and Event Handling

The central feature of any user interface is the window. In Windows CE-based platforms with a graphical display, the window is the rectangular area of the screen where an application displays output and receives user input. However, even applications running on devices that lack a graphical display require windows to receive messages from the OS. To help manage the windows used in an application, Windows CE supports common Win32 User Interface APIs, tailored for the smaller display size of typical Windows CE-based devices. The following table shows the similarities and differences between the Win32 API and Windows CE.

Win32 User Interface
Windows CE
Owned windows | Full dialog box support
Sends nonclient messages to applications | Not supported
Client area | Maximum area to support a command bar in client area and controlled by application
Multiple Document Interface (MDI) | Not supported
Cascading menus and bitmap menus | Not supported
Owner-drawn menus | Not supported
Icons of any size | Supported

The OS controls how the nonclient area is drawn and managed; Windows CE does not send applications messages dealing with the nonclient area of the window.


Event Handling

Windows CE is an event-driven OS. Each message is passed in the message structure MSG. The Windows CE structure MSG contains the following six members. Message hooks are not supported. The following table shows supported members.

Member
Description
hwnd | Handle to the window whose window procedure receives the message.
message | Specifies the message number.
wParam | Specifies additional message data. The exact meaning depends on the value of the message member.
lparam | Specifies additional message data. The exact meaning depends on the value of the message member.
time | Specifies the time the message was posted.
pt | Specifies the coordinates of the last position touched on the screen, when the message was posted.


GDI Support

The GDI is the GWES subsystem that controls the display of text and graphics. Use GDI to draw lines, curves, closed figures, text, and bitmap images.

GDI uses a device context to store data it requires to display text and graphics on a specified device. The graphic objects stored in a device context include a pen for line drawing, a brush for painting and filling, a font for text output, a bitmap for copying or scrolling, a palette for defining the available colors, and a clipping region. Windows CE supports printer device contexts for drawing on printers, display device contexts for drawing on video displays, and memory device contexts for drawing into memory.

The following table shows GDI features supported by Windows CE.

GDI feature
Description
Raster and TrueType fonts | TrueType fonts are scalable and rotatable. Seven rasterized system fonts are available in several sizes in ROM. You can also add your own raster fonts. Windows CE supports only one category of font, either raster or TrueType, on a specified system.
Custom color palettes and both palletized and nonpalletized color display devices | Supports color bit depths of 1, 2, 4, 8, 16, 24, and 32 bits per pixel (bpp). The 2-bpp color bit depth is unique to Windows CE.
Bit block transfer functions and raster operation codes | Enables you to transform and combine bitmaps.
Pens and brushes | Supports dashed, wide, and solid pens; patterned brushes.
Printing | Supports graphics printing.
Cursors | Supports full use of cursors, including user-defined cursors, or just the wait cursor.
Shape drawing functions | Supports the ellipse, polygon, rectangle, and round rectangle shapes.

Windows CE GDI does not support the following features:

  • Transformation functions of coordinate space, such as SetMapMode, GetMapMode, SetViewportExt, and SetWindowExt. Coordinate space is equivalent to device space.
  • World Transform API
  • MoveTo and LineTo functions
  • Color cursors
  • Animated cursors


User Input Support

You can configure Windows CE to meet the user input requirements of different platforms. Currently, the keyboard, input panel, voice, mouse, and stylus are the supported input methods on Windows CE-based devices.

Keyboard features in Windows CE-based devices are similar to Windows-based desktop platforms. Like those platforms, Windows CE supports hot keys. A hot key is a keystroke or combination of keystrokes that shifts the user to a different application. Hot keys give the user high-priority system access for a specific purpose, such as canceling a time-consuming file transfer operation.


Working with Windows and Messages

The appearance and behavior of a window is largely determined by its inherent attributes and relationship to other windows. You assign attributes to a window by setting window styles and extended styles and by calling functions that alter window attributes.

Windows are always rectangular. They are placed above and below each other along an imaginary line that runs perpendicular to the screen. This stack of windows is called the z-order. Each window has a unique position in the z-order. Windows that appear first in the z-order are considered to be in front of, or on top of, windows that appear later in the z-order. A window’s position in the z-order affects its appearance. A window may partially or totally obscure another depending on its location, size, and position in the z-order.

A window is divided into a nonclient area—occupied by borders, scroll bars, and various other controls—and a client area, the central space inside the nonclient area. You can draw in the client area, but not in the nonclient area. In Microsoft Windows CE, the nonclient area of a window is controlled exclusively by the window manager. Windows CE does not send applications messages dealing with the nonclient area. The following screen shot shows the nonclient and client areas.

A window can be displayed or hidden, depending on whether its WS_VISIBLE style is turned on or off. A window that has the WS_VISIBLE style turned off will not be displayed on the screen. A window that has the WS_VISIBLE style turned on might or might not be displayed on the screen, depending on whether it is obscured by other windows. Covering or uncovering a window with another window does not change the WS_VISIBLE style.

Every window has a unique identifier called a window handle. When you create a window, you receive a window handle, which you can then use to call functions that use the window. Handles are useful in applications that create multiple child windows. You can change the window handle by calling the SetWindowLong function and retrieve the handle by calling the GetWindowLong function.

All applications have at least one window, regardless of whether they have a graphical user interface. This is because Windows CE is a message-driven operating system and a window is the means by which an application receives messages. Each window must be associated with a special function called a window procedure or WinProc. Windows CE calls this window procedure to pass a message to the application.

A message consists of a message identifier and optional parameters. A message identifier is a named constant that identifies a message. When a window procedure receives a message, it uses a message identifier to determine how to process the message. For example:

  • WM_CREATE is sent to a window when it is created.
  • WM_DESTROY is sent to a window when it is destroyed.
  • WM_PAINT is sent to a window when the window client area has changed and must be repainted.

Message parameters contain data, or the location of data, that a window procedure uses to process messages. The meaning and value of the message parameters depend on the message identifier. A message parameter can contain an integer, packed bit flags, a pointer to a structure containing additional data, or other information. A window must check the message identifier to determine how to interpret the message parameters. The term “message” is used to mean either the message identifier or the identifier and the parameters together. The specific meaning is usually clear from the context.

The system sends a message to a window procedure by passing the message data as arguments to the procedure. The window procedure then performs an appropriate action for the message; it checks the message identifier and, while processing the message, uses data specified by the message parameters.

If a window procedure does not process a message, it should pass the message along for default processing. The window procedure does this by calling the DefWindowProc function, which performs a default action and returns a message result. The window procedure must then return this value as its own message result. Most window procedures process just a few messages and pass the others on to DefWindowProc.

Window procedures can be shared. The handle of the specific window receiving the message is available as an argument of the window procedure.

In addition to having a window procedure, every Windows CE-based application must have WinMain as its entry point function. WinMain performs a number of tasks, including registering the window class for the main window and creating the main window. WinMain registers the main window class by calling the RegisterClass function, and it creates the main window by calling the CreateWindowEx function. WinMain does not need to do these things itself; it can call other functions to perform any or all of these tasks. For a code example of a WinMain function, see Creating a Sample Application.

One task WinMain must perform itself is to establish a message loop. The message loop retrieves messages from a thread message queue and dispatches them to the appropriate window procedure. The message queue coordinates the transmission of messages for a specified thread. Every thread can have only one message queue. When a message is passed to a window, it is placed on the message queue of the window thread. The thread receives and dispatches the message. There are two ways to pass a message to a window: posting a message and sending a message.


Posting Messages

To post a message, call the PostMessage function. PostMessage combines the message identifier and parameters into a single message and places it on the receiving window message queue. Eventually, the receiving window message loop removes the message from the message queue and dispatches it to the appropriate window procedure.

PostMessage is an asynchronous function. Windows CE does not synchronize between the sending thread and the receiving thread for posted messages. When the call to PostMessage returns, there is no guarantee the window procedure for the receiving window has processed the message. In fact, if the message was posted to the same thread, the window procedure has not processed the message.

You can post a message without specifying a window. If you supply a NULL window handle when you call the PostMessage function, the message is posted to the queue associated with the current thread; because no window handle is specified, you must process the message directly from the message loop. This creates a message that applies to the entire application instead of a specific window.


Sending Messages

To send messages to a window, call the SendMessage function. Unlike PostMessage, SendMessage is a synchronous function. It does not return until the window procedure of the receiver window has processed the message.

You typically send a message when you want a window procedure to perform a task immediately. The SendMessage function sends the message directly to the window procedure of the receiving window. The SendMessage function waits until the window procedure completes processing and then returns the message result. Parent and child windows often communicate by sending messages to each other. For example, a parent window that has an edit control as its child window can set the text of the control by sending a message to it. The control can notify the parent window of user-initiated changes to the text by sending messages back to the parent.

If the receiving thread is the same as the sending thread, SendMessage calls the window procedure directly. If the receiving thread is a different thread from the sending thread, the two message queues synchronize the message passing. The sending thread does not continue executing until the receiving thread processes the message. The receiving thread does not process the message if it is not executing a message loop. Consequently, if you send a message to a window in a thread not executing a message loop, the sending thread stops responding.


Receiving and Dispatching Messages

To receive messages, call the GetMessage function. When a thread calls GetMessage, Windows CE examines the thread message queue for incoming messages. Windows CE processes messages in the following order:

  1. Windows CE checks for messages placed on the queue by the SendMessage function. After the system removes the message from the queue, it dispatches the message to the appropriate window procedure from within the GetMessage function. This guarantees the sender and receiver message queues remain synchronized. The receiver must call GetMessage for the sent messages to be processed.
  2. If no sent message is found, Windows CE checks for messages placed on the queue by a call to PostMessage.
  3. If no posted message is found, Windows CE checks the queue for messages posted by the user input system.

    By processing user input messages at a lower priority, the system guarantees that each input message and any posted messages that result from it are processed completely before moving on to the next input message.

  4. If no posted input messages are found, Windows CE checks the queue for WM_QUIT messages placed on the queue by a call to PostQuitMessage.
  5. If no posted quit messages are found, Windows CE checks the queue for WM_PAINT messages placed on the queue by the windowing system.
  6. If no paint messages are found, Windows CE checks the queue for WM_TIMER messages placed on the queue by the timer system.

When GetMessage receives any of the previous messages, it returns the message content. The thread must call DispatchMessage to dispatch the message to the correct window procedure. If the message is a WM_QUIT message, the return value of GetMessage is zero, which causes the thread to end its message loop.

The system dispatches messages in the GetMessage call of the message loop, and the application dispatches messages by calling the DispatchMessage function in the message loop.

You may need to process messages you receive from GetMessage before you send them out using DispatchMessage. The most common processing routines are TranslateMessage, TranslateAccelerator, and IsDialogMessage. Some of these routines can dispatch messages internally because the application no longer needs to call DispatchMessage in the main message loop.

You usually call TranslateMessage before DispatchMessage. TranslateMessage determines which characters go with keyboard messages. TranslateMessage posts the characters to the message queue to be picked up on the next pass of the message loop.

To intercept keyboard messages and generate menu commands, call the TranslateAccelerator function. Call the IsDialogMessage function to ensure the proper operation of modeless dialog boxes.

You can remove a message from its queue with the GetMessage function. Call the PeekMessage function to examine a message without removing it from its queue. This function fills an MSG structure with information about the message. Use the PeekMessage function carefully because it does not block the waiting for a message event, which enables an application to continue processing regardless of messages in the queue. In a Windows CE-based application, if an application does not block the waiting for a message or some other event, the kernel cannot shift the CPU into low-power mode; this can quickly drain the device batteries.

When processing messages, Windows CE supports both system-defined messages and application-defined messages. System-defined messages have message identifiers ranging from 0 through 0x3ff. Messages with message identifiers ranging from 0x400 through 0x7fff are available for application-defined messages.

There are two types of system-defined messages: general window messages, which are used for all windows, and special purpose messages, which apply to a particular class of windows. General window messages cover a wide range of information and requests, including messages for input device and keyboard input as well as window creation and management.

The prefix of the symbolic constant for the message generally identifies the category to which the message belongs. For example, general window messages all start with WM, whereas messages that apply only to button controls start with BM.

The following table shows Windows CE message types.

Message type
Description
BM | Button message
BN | Button notification
CB | Combo box message
CBN | Combo box notification
CDM | Common dialog box message
CDN | Common dialog box notification
CPL | Control panel message
DB | Object store message
DM | Dialog box default push button message
DTM | Date time picker and HTML viewer messages
DTN | Date time picker notification
EM | Edit control message
EN | Edit control notification
HDM | Header control message
HDN | Header control notification
IMN | Input context message
LB | List box control message
LBN | List box notification
LINE | Line device message
LVM | List view message
LVN | List view notification
MCM | Month calendar message
MCN | Month calendar notification
NM | Messages sent by a variety of controls
PBM | Progress bar message
PSM | Property sheet message
PSN | Property sheet notification
RB | Rebar message
RBN | Rebar notification
SB | Status bar window message
SBM | Scroll bar message
STM | Static bar message
STN | Static bar notification
TB | Toolbar message
TBM | Trackbar message
TBN | Trackbar notification
TCM | Tab control message
TCN | Tab control notification
TVM | Tree view message
TVN | Tree view notification
UDM | Up-down control message
UDN | Up-down control notification
WM | General window messages

You can define messages for use by your own application’s window. If you create messages, be sure the window procedure that receives them interprets and processes them correctly. The OS does not interpret application-defined messages.

In some situations, you need to use messages to communicate with windows controlled by other processes. In this situation, call the RegisterWindowMessage function to register a message identifier. The message number returned is guaranteed to be unique throughout the system. By using this function, you prevent the conflicts that can arise if different applications use the same message identifier for different purposes.

Windows CE does not support hooking messages because the extra processing required by hooks can impair the performance of Windows CE-based devices.

When handling messages in your application, be aware of the WM_HIBERNATE message. Windows CE defines a WM_HIBERNATE message to notify an application when system resources run low. When an application receives this message, it should attempt to release as many resources as possible. The system checks memory status at five-second intervals. Every Windows CE-based application that uses even moderate amounts of system resources should implement a handler for the WM_HIBERNATE message. If an application window is not visible, it cannot receive a WM_HIBERNATE message. This is because the WM_HIBERNATE message is sent only to applications that have a button on the taskbar, which only visible windows do. A hidden window will not get this message, even if it is a top-level, overlapped window.


Creating a Window

Every window is a member of a window class. A window class is a template for creating a window. When you write an application, you must register all window classes used to create windows. To simplify the process of creating windows, Windows CE includes several system-defined window classes; because Windows CE registers these classes automatically, you can immediately create windows with them.

You create windows with the CreateWindow or CreateWindowEx function. The only difference between these functions is that CreateWindowEx supports the extended style parameter, dwExStyle, while CreateWindow does not. These functions take a number of parameters that specify the attributes of the window being created. In Windows CE, CreateWindow is implemented as a macro that calls CreateWindowEx.

Windows CE includes additional functions, including DialogBox, CreateDialog, and MessageBox, for creating special-purpose windows such as dialog boxes and message boxes.

The CreateWindowEx function has the following syntax:

HWND
CreateWindowEx(
   DWORD       dwExStyle,       //Extended style parameter
   LPCWSTR     lpClassName,     //Class name parameter
   LPCWSTR     lpWindowName,    //Window name parameter
   DWORD       dwStyle,         //Style parameter
   int         X,               //Horizontal parameter
   int         Y,               //Vertical parameter
   int         nWidth,          //Width parameter
   int         nHeight,         //Height parameter
   HWND        hwndParent,      //Parent parameter
   HMENU       hMenu,           //Menu parameter
   HINSTANCE   hInstance,       //Instance handle parameter
   LPVOID      lpParam);        //Creation data parameter

The following table shows the window attributes in CreateWindowEx.

Window attribute
Description
Extended style | The dwExStyle parameter specifies one or more window extended styles. These have their own set of WS_EX_* flags and should not be confused with the WS_* flags.
Class name | Every window belongs to a window class. Except for built-in classes, such as controls, an application must register a window class before creating any windows of that class. The lpClassName parameter specifies the name of the class used as a template for creating the window.
Window name | The window name, also called window text, is a text string associated with a window. The lpWindowName parameter specifies the window text for the newly created window. Windows use this text in different ways. A main window, dialog box, or message box typically displays its window text in its title bar. A button control, edit control, or static control displays its window text within the rectangle occupied by the control. A list box, combo box, or scroll bar control does not display its window name. All windows have the text attribute, even if they do not display the text.
Style | The dwStyle parameter specifies one or more window styles. A window style is a named constant that defines an aspect of the window’s appearance and behavior. For example, a window with the WS_BORDER style has a border around it. Some window styles apply to all windows; others apply only to windows of specific window classes.
Horizontal and vertical coordinates | The x and y parameters specify the horizontal and vertical screen coordinates, respectively, of the window’s upper-left corner.
Width and height coordinates | The nWidth and nHeight parameters determine the width and height of the window in device units.
Parent The hwndParent parameter specifies the parent or the owner of a window, depending on the style of the flags passed in.

If neither WS_POPUP nor WS_CHILD is specified, the hwndParent parameter may be a valid window handle or NULL. If the parameter is NULL, the new window is a top-level window without a parent or owner. If it is non-NULL, the new window is created as a child of the specified parent window.

If WS_CHILD is specified, the hwndParent parameter must be a valid window handle. The new window is created as a child of the parent window.

If the WS_POPUP style is specified, the new window is created as a top-level window and the hwndParent parameter specifies the owner window. If WS_POPUP is specified and the parameter is NULL, the new window is partially owned by Windows CE. The WS_POPUP style overrides the WS_CHILD style.
Menu | Windows CE does not support menu bars. In Windows CE, you can use the hMenu parameter to identify a child window. Otherwise, it must be NULL.
Instance handle | The hInstance parameter identifies the handle of the specific instance of the application that creates the window.
Creation data | Every window receives a WM_CREATE message when it is created. The lpParm parameter is passed as one of the message parameters. Although it can be any value, it is most commonly a pointer to a structure that contains data required to create a particular window.

The class name for a new window class has to be a Unicode string. You can use the TEXT macro to cast a string as Unicode, as in TEXT("classname").

The system does not automatically display the main window after creating it. Rather, the application’s WinMain function uses the ShowWindow function to display the window. An application uses the SetWindowText function to change the window text after it creates the window. It uses the GetWindowTextLength and GetWindowText functions to retrieve the window text from a window.

For an example of how to call the CreateWindowEx function, see Creating a Sample Application.


Window Relationship Fundamentals

When you create a window, you need to specify a window style. The style you select determines the relationship that window has with other windows in your application. For example, you can designate a window as a child of another window by specifying the WS_CHILD style. A child window is a window that appears only within the client area of its parent window. A child window has only one parent, but a parent window can have any number of child windows and these, in turn, can have their own child windows.

A child window that can trace a relationship to a parent window through a chain of parent-child window relationships, however long, is said to be a descendant of the parent window. Likewise, a parent window that can trace a relationship to a child window through a chain of parent-child windows is said to be an ancestor window of that child window. To determine whether a window is a descendant window of a specified parent window, call the IsChild function.

You can change the parent window of an existing child window by calling the SetParent function. When you do, the system removes the child window from the client area of the existing parent window and moves it to the client area of the new parent window. The GetParent function retrieves the handle to a window’s parent window.

Windows CE has rules governing the display and behavior of parent and child windows. For example, a child window is positioned relative to the upper-left corner of its parent’s client rectangle. Child windows are always placed directly in front of their parent windows and are always kept with their parents in the z-order. When the z-order of a parent window is changed, child windows automatically move with their parent.

Although you can place or size a child window outside of a parent window, a child window cannot draw any part of itself outside of its parent’s client rectangle. In Windows CE, a parent window cannot draw on its children, and a window cannot draw on siblings in front of it. In other words, all windows behave as if they have the WS_CLIPCHILDREN and WS_CLIPSIBLING styles. You can avoid some of these restrictions by using the GetDCEx function. This function enables you to obtain a handle to a device context for the client area of a specified window and to control how, or whether, clipping occurs. For example, when used with either WA_CLIPSIBLINGS or DCX_CACHE, GetDCEx enables a child window to scroll with its parent.

A window that has no parent is called a top-level window. Windows that have the same parent are sibling windows. Even though they may be in different applications, all top-level windows are considered siblings. Top-level windows are parented to an invisible dummy root window.

The following screen shot shows the parent-child window relationship.

A window can also be defined as another window’s owner; thus, there can be an owner window and an owned window. Although the relationship between an owner window and an owned window is similar to the relationship between a parent and child window, there are some differences. For example, the owner-owned relationship can exist only between top-level windows and, unlike child windows, owned windows can draw outside of their owners.

You can create an owner-owned relationship between top-level windows when you create a window with the WS_POPUP style. Because top-level windows do not have parents, the window you specify as the parent when you call the CreateWindow function becomes the owner of the new window. Owned windows can in turn own other windows. To return the owner of a specified window, call the GetParent function. When a window is destroyed, owned windows are also destroyed.

Owner-owned windows move as a group. If you move a window forward in the z-order, its owner window and owned windows move forward with it. Windows CE places owned windows in front of their owners. Although Windows CE does not prevent you from inserting a top-level window between an owner window and an owned window, it does keep owned groups of windows together when one is moved in the z-order. This means when you change a window’s placement in the z-order, Windows CE displaces any windows between the window and its owned or owner windows. Moving or sizing a window does not affect the location or size of its owner or owned windows.

You can create a WS_POPUP window with a NULL owner. When you do, the window becomes partially owned by the desktop. If Windows CE moves the desktop to the top of the z-order, these windows remain on top of the desktop. However, if you move the window to the top of the z-order, it does not pull the desktop with it. Threads in the system that do not usually have any kind of window interface use this style when they need to display a message to the user. Owned windows typically do not have taskbar buttons. However, if you create a WS_POPUP window with a NULL owner, a taskbar button will appear in the taskbar. Be sure when specifying the WS_POPUP style that you do not specify the WS_CHILD style as well. WS_POPUP and WS_CHILD windows are incompatible and should not be used together.


Displaying a Window

You can control a window’s visibility by using the ShowWindow or SetWindowPos functions or by turning its WS_VISIBLE style on or off. Think of the WS_VISIBLE style as a way to hide a window. If this style is turned off, neither the window nor its descendants will be drawn on the screen. Even though a child window is hidden when its parent is hidden, the child window’s WS_VISIBLE style is not changed when its parent’s style is changed. A child window may have the WS_VISIBLE style turned on and still not be visible if it has a parent or ancestor window with a WS_VISIBLE style turned off.

To determine if a window is visible, call the IsWindowVisible function. This function checks the window and its ancestors to determine if the window is visible. A window may be considered visible but may not appear on the screen if covered by other windows.

By default, the CreateWindowEx function creates a hidden window unless you specify the WS_VISIBLE style. Typically, an application sets the WS_VISIBLE style after it has created a window to keep details of the creation process hidden from the user. For example, an application may keep a new window hidden while it customizes the window appearance.

Changing the visibility of a window does not automatically change the visibility of windows it owns. Also, if you create a dialog box whose parent window is not visible, the dialog box will be visible. To avoid this inconsistency, do not create a dialog box owned by an invisible window.


Sizing and Positioning a Window

A window’s size and position are expressed as a bounding rectangle specified in coordinates relative to the screen or to the parent window. Typically, window dimensions and coordinates are measured in pixels.

When you create a window, you can set the initial size and position of the window directly or instruct the system to calculate the initial size and position by specifying CW_USEDEFAULT in the CreateWindow or CreateWindowEx function. After creating a window, set the window size or position by calling the MoveWindow or SetWindowPos function.

If you need to create a window with a client area of a specified size, call the AdjustWindowRectEx function to calculate the required size of a window based on the desired size of the client area. Pass the resulting size values to the CreateWindowEx function.

Although you can create a window of any size, it should not exceed the screen size of the target device. Before setting a window size, check the width and height of the screen by using GetSystemMetrics with the SM_CXSCREEN and SM_CYSCREEN flags.

You can call the GetWindowRect function to retrieve the coordinates of a window’s bounding rectangle. GetWindowRect fills a RECT structure with the coordinates of the window’s upper-left and lower-right corners. The coordinates are relative to the upper-left corner of the screen, even for a child window. The ScreenToClient or MapWindowPoints function maps the screen coordinates of a child window’s bounding rectangle to coordinates relative to the parent window’s client area.

The GetClientRect function retrieves the position and size of a window client area; because coordinates are relative to the client area itself, the client area’s upper-left corner is always at coordinates (0, 0) and the coordinates of the lower-right corner are the width and height of the client area. Furthermore, because the command bar is part of the client area in Windows CE, it is included in the dimensions returned by the GetClientRect function.

To retrieve the handle to the window that occupies a particular point on the screen, call the WindowFromPoint function. Call the ChildWindowFromPoint function to retrieve the handle to the child window that occupies a specified point in the parent window client area. To convert the client coordinates of a specified point to screen coordinates, call the ClientToScreen function. To convert the screen coordinates of a specified point into client coordinates, call the ScreentoClient function.

To change a window’s position in the z-order, call the SetWindowPos function. This function is used to create a topmost window. A topmost window is a window that has the WS_EX_TOPMOST style. Do not confuse topmost with top-level. Top-level refers to whether or not a window has a parent, whereas topmost refers to a specific style that controls the z-order for the window. Topmost windows are above all non-topmost sibling windows in the z-order. To create a topmost window, specify the WS_EX_TOPMOST style when you create the window, or call SetWindowPos and set the hWndInsertAfter parameter to HWND_TOPMOST.

A window may lose its topmost style by calling SetWindowPos and setting the hWndInsertAfter parameter to HWND_NOTOPMOST. If a window is positioned directly after a non-topmost window, then that window loses its WS_EX_TOPMOST style. You can set the SetWindowLong function to give a window the WS_EX_TOPMOST style; however, this function does not change the window’s z-order.

The Defer Window API, which consists of DeferWindowPos, BeginDeferWindow, and EndDeferWindowPos, is an alternative to making repeated calls to SetWindowPos. These functions enable you to queue up several window position changes and execute them simultaneously.

Occasionally, topmost windows can disappear behind the desktop. This typically occurs when one of the following rules governing the use of topmost windows is broken:

  • When creating an owned window, if the owner of the window is designated WS_EX_TOPMOST, then the owned window must be a topmost window as well.
  • When calling SetWindowPos to change the topmost attribute of a window, you must change the topmost attribute of all of its owned windows.
  • When changing the topmost attribute of a window, do not call SetWindowLong.
  • When changing the z-order of a member of an owned group of windows, you must respect the topmost attribute of other windows.


Destroying a Window

To destroy a window, call the DestroyWindow function. When a thread or process terminates, Windows CE removes all windows owned by that thread or process. Windows that are removed when a thread or process terminates do not always receive WM_DESTROY messages. For this reason, it is recommended you manually destroy windows. When a window is destroyed, the system hides the window, sends a WM_DESTROY message to the window procedure of the window being destroyed, and removes any associated internal data. The window handle becomes invalid and can no longer be used by the application.

Destroying a window automatically destroys the window’s descendant windows. The DestroyWindow function first sends a WM_DESTROY message to the initial window being destroyed and then to its descendant windows.

You should destroy any window no longer needed. Before destroying a window, save or remove any data associated with the window and release system resources allocated to the window.

Destroying a window does not affect the window class from which the window is created. You can still create new windows by using the class, and any existing windows of that class continue to operate.


Creating a Sample Application

This section contains a code example used to create a simple Windows CE-based application. This sample application demonstrates the basic framework common to all Windows CE-based applications. It begins executing with the WinMain function, which performs the following tasks:

  1. WinMain places the application-instance handle in a global variable. Because this handle is used in various places throughout an application, it is common to place it in a global variable accessible to all functions. The smallest possible interval a timer can measure is the system-tick interval.
  2. WinMain calls the application-defined InitApplication function, which then calls the RegisterClass function to register the application’s main window class. More complicated applications may need to register more window classes and determine if other instances of the application are running.
  3. WinMain calls the application-defined InitInstance function, which then calls the CreateWindow function to create a window. CreateWindow returns a window handle identifying the new window. This handle is used to refer to the window in subsequent function calls.
  4. WinMain creates the message loop by calling the GetMessage, TranslateMessage, and DispatchMessage functions in the format displayed in the sample application. This loop receives messages and dispatches them to the window procedures.

Note that the application does not directly call the window procedure, MainWndProc. The system calls this function as the message loop receives and dispatches messages. In this application, MainWndProc processes only the WM_CLOSE message that tells the window to close. When the window receives a WM_CLOSE message, it calls the PostQuitMessage function, which then calls GetMessage to return FALSE. This, in turn, causes the message loop to terminate and the application to exit.

Windows CE sends many other messages to the window besides WM_CLOSE. MainWndProc passes all other messages to the DefWindowProc function, the default window procedure provided by the system. Pass all messages to DefWindowProc that you do not process yourself; otherwise, your window may not function correctly. The following code example provides a framework for creating a Windows CE-based application.

#include <windows.h>

HINSTANCE g_hInst = NULL;     // Handle to the application instance
HWND g_hwndMain = NULL;       // Handle to the application main window
TCHAR g_szTitle[80] = TEXT ("Main Window"),
                              // Application main window name
      g_szClassName[80] = TEXT ("Main window class");
                              // Main window class name

/***********************************************************************

FUNCTION:
  WndProc

PURPOSE:
  The callback function for the main window. It processes messages sent
  to the main window.

***********************************************************************/
LRESULT CALLBACK WndProc (HWND hwnd, UINT umsg, WPARAM wParam,
                          LPARAM lParam)
{
  switch (umsg)
  {
    // Add cases such as WM_CREATE, WM_COMMAND, WM_PAINT if you don't
    // want pass these messages along for default processing.

    case WM_CLOSE:
      DestroyWindow (hwnd);
      return 0;

    case WM_DESTROY:
      PostQuitMessage (0);
      return 0;
  }

  return DefWindowProc (hwnd, umsg, wParam, lParam);
}

/***********************************************************************

FUNCTION:
  InitInstance

PURPOSE:
  Create and display the main window.

***********************************************************************/
BOOL InitInstance (HINSTANCE hInstance, int iCmdShow)
{

  g_hInst = hInstance;

  g_hwndMain = CreateWindow (
                  g_szClassName,  // Registered class name
                  g_szTitle,      // Application window name
                  WS_OVERLAPPED,  // Window style
                  0,              // Horizontal position of window
                  0,              // Vertical position of window
                  CW_USEDEFAULT,  // Window width
                  CW_USEDEFAULT,  // Window height
                  NULL,           // Handle to parent window
                  NULL,           // Handle to menu identifier
                  hInstance,      // Handle to application instance
                  NULL);          // Pointer to window-creation data

  // If it failed to create the window, return FALSE.
  if (!g_hwndMain)
    return FALSE;

  ShowWindow (g_hwndMain, iCmdShow);
  UpdateWindow (g_hwndMain);
  return TRUE;
}

/***********************************************************************

FUNCTION:
  InitApplication

PURPOSE:
  Declare the window class structure, assign values to the window class
  structure members and register the window class.

***********************************************************************/
BOOL InitApplication (HINSTANCE hInstance)
{
  WNDCLASS wndclass;

  wndclass.style = CS_HREDRAW | CS_VREDRAW;
  wndclass.lpfnWndProc = (WNDPROC)WndProc;
  wndclass.cbClsExtra = 0;
  wndclass.cbWndExtra = 0;
  wndclass.hIcon = NULL;
  wndclass.hInstance = hInstance;
  wndclass.hCursor = NULL;
  wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
  wndclass.lpszMenuName = NULL;
  wndclass.lpszClassName = g_szClassName;

  return RegisterClass (&wndclass);
}

/***********************************************************************

FUNCTION:
  WinMain

PURPOSE:
  The WinMain function of the application. It is called by the system as
  the initial entry point for this WindowsCE-based application.

***********************************************************************/
int WINAPI WinMain (
               HINSTANCE hInstance,     // Handle to current instance
               HINSTANCE hPrevInstance, // Handle to previous instance
               LPWSTR lpCmdLine,        // Pointer to command line
               int iCmdShow)            // Shows the state of the window
{
  MSG msg;                          // Message structure
  HACCEL hAccel;                    // Handle of the accelerator table

  if (!hPrevInstance)
  {
    if (!InitApplication (hInstance))
      return FALSE;
  }

  if (!InitInstance (hInstance, iCmdShow))
    return FALSE;

  // Insert code here to load the accelerator table.
  // hAccel = LoadAccelerators (...);

  while (GetMessage (&msg, NULL, 0, 0))
  {
    if (!TranslateAccelerator (
                    g_hwndMain,     // Handle to the destination window
                    hAccel,         // Handle to the accelerator table
                    &msg))          // Address of the message data
    {
      TranslateMessage (&msg);
      DispatchMessage (&msg);
    }
  }

  return msg.wParam;
}


Using Resources

Resources are objects used within an application, but defined outside an application. They are added to the executable file when the application is linked. Microsoft Windows CE resources include menus, keyboard accelerators, dialog boxes, carets, cursors, icons, bitmaps, string-table entries, message-table entries, timers, and user-defined data. The Windows CE-based platform targeted determines available resources.

Resource files have an .rc extension. Text-based resources, such as menus, can be created using a text editor. Resources, such as icons, are generated using a resource editor. Resources created using a resource editor must be referenced in the .rc file associated with your application. Resource files contain a special resource language, or script, that must be compiled by a resource compiler. The resource compiler converts the .rc file into a resource (.res) file, and then links the file to the application. For more information about including a resource file with your application, see your compiler User’s Guide.

Regardless of how a resource is compiled, you must load resources into memory before you can use them. The FindResource function finds a resource in a module and returns a handle to the binary resource data. The LoadResource function uses the resource handle returned by FindResource to load the resource into memory. After you load a resource by using LoadResource, Windows CE automatically unloads and reloads the resource as memory conditions and application execution require. Thus, you need not explicitly unload a resource you no longer need.

To find and load any type of resource in one call, use the FindResource and LoadResource functions; use these functions only if you must access the binary resource data for subsequent function calls. The following table shows the resource-specific functions you can use.

Function
Description
FormatMessage | Loads and formats a message-table entry
LoadAccelerators | Loads an accelerator table
LoadBitmap | Loads a bitmap resource
LoadCursor | Loads a cursor resource
LoadIcon | Loads an icon resource
LoadImage | Loads an icon, cursor, or bitmap
LoadMenu | Loads a menu resource
LoadString | Loads a string-table entry

Before terminating, an application should release the memory occupied by accelerator tables, bitmaps, cursors, icons, and menus. The following table shows functions an application can use to do this.

Resource
Release function
Accelerator table | DestroyAcceleratorTable
Bitmap | DeleteObject
Cursor | DestroyCursor
Icon | DestroyIcon
Menu | DestroyMenu


Creating Menus

A menu is a list of options called menu items from which a user can select to perform an action. Choosing a menu item opens a submenu or causes the application to execute a command. Virtually all main windows contain some type of menu. In many Windows CE-based applications, these menus are placed in a command bar, but you can also place a menu directly on the window itself. A command bar combines the features of a menu bar and a toolbar in one control. For more information about creating a command bar, see Creating Controls.

In addition to standard menus and command bar menus, some Windows CE-based applications include scrolling menus. If a menu does not fit on the screen, Windows CE adds scrolling arrows so that users can scroll up and down through the menu. When the user cannot scroll any further in one direction or the other, the associated arrow is dimmed. An application dims unavailable items to provide a visual indicator that a selection is not available. Pressing the up or down arrow scrolls through the menu one item at a time. No menu item is highlighted while scrolling. Changing the selection by using a keyboard arrow or keyboard mnemonic causes the newly selected item to scroll into view if it is not already displayed. If a menu has too many columns to fit the width of the display area, Windows CE ignores all column breaks and converts the menu into a single-column scrolling menu. If an individual menu item is too large to be drawn without being clipped by the up or down arrow, the item is not drawn. This might leave a large blank space next to an arrow.

Regardless of whether a menu is standard or scrolling, all menus in Windows CE are implemented as a top-level, pop-up window. A pop-up window menu is a floating menu that displays commands specific to the object selected by the user or to the object’s immediate context. Each menu must have an owner window. Windows CE sends a WM_COMMAND message to a menu’s owner window when the user selects the menu or chooses an item from the menu. When a user selects a menu item that opens a submenu, Windows CE does not send a command message to the menu’s owner window. Rather, Windows CE sends a WM_INITMENUPOPUP message before displaying the submenu. Obtain the handle to the submenu associated with an item by using the GetSubMenu or GetMenuItemInfo function.

There are two ways to create a menu: define a menu template in your resource file, or use menu creation functions.


Defining a Menu Template

A menu template defines a menu, including all associated menu items and submenus, in a resource file. Implementing a menu as a resource makes an application easier to localize because only the resource-definition file needs to be localized for each language, and not the application source code. The following code example shows the syntax for menu resource definitions.

menuID MENU [[optional-statements]] { item-definitions . . . }

Here, menuID is either a unique string or unique 16-bit unsigned integer that identifies the menu, optional-statements specify options you can a include when creating a menu, and item-definitions are used to create menu items.

There are two types of menu items you can create: MENUITEMs and POPUPs. A MENUITEM specifies a final selection; a POPUP specifies a popup submenu, which also may contain MENUITEMs and POPUPs. The following code example shows the syntax for the for these two menu items.

MENUITEM text, result, [[optionlist]] MENUITEM SEPARATOR
POPUP text, [[optionlist]] { item-definitions . . . }

Here, text is a string that contains the name of the menu, optionlist is a parameter that specifies the appearance of the menu, such as checked or dimmed, and result is the number generated when the user selects the menu item. This parameter accepts an integer value and returns an integer; when the user selects the menu item name, the result is sent to the window that owns the menu.

Windows CE provides a special type of menu item, called a separator, that appears as a horizontal line. You can use a separator to divide a menu into groups of related items. The MENUITEM SEPARATOR form of the MENUITEM statement creates a separator. A separator cannot be used in a command bar, and the user cannot select a separator.

The following code example is a complete MENU statement.

#define IDR_CEPADMENU                   101
#define IDM_NEW                         40001
#define IDM_OPEN                        40002
#define IDM_SAVE                        40003
#define IDM_SAVEAS                      40004
#define IDM_EXIT                        40005
#define IDM_ABOUT                       40006
#define IDM_UNDO                        40007
#define IDM_CUT                         40008
#define IDM_COPY                        40009
#define IDM_PASTE                       40010
#define IDM_CLEAR                       40011
#define IDM_SELECTALL                   40012
#define IDM_FIND                        40013
#define IDM_FINDNEXT                    40014
#define IDM_REPLACE                     40015

IDR_CEPADMENU MENU DISCARDABLE
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&New                Ctrl+N",            IDM_NEW
        MENUITEM "&Open...        Ctrl+O",            IDM_OPEN
        MENUITEM "&Save            Ctrl+S",       IDM_SAVE
        MENUITEM "Save &As...",                        IDM_SAVEAS
        MENUITEM SEPARATOR
        MENUITEM "&Exit",                                IDM_EXIT
    END

    POPUP "&Edit"
    BEGIN
        MENUITEM "&Undo            Ctrl+Z",            IDM_UNDO
        MENUITEM SEPARATOR
        MENUITEM "Cu&t                Ctrl+X",            IDM_CUT
        MENUITEM "&Copy            Ctrl+C",            IDM_COPY
        MENUITEM "&Paste            Ctrl+V",            IDM_PASTE
        MENUITEM "Clea&r            Del",                IDM_CLEAR
        MENUITEM SEPARATOR
        MENUITEM "Select A&ll    Ctrl+A",            IDM_SELECTALL
    END

    POPUP "&Search"
    BEGIN
        MENUITEM "&Find...        Ctrl+F",            IDM_FIND
        MENUITEM "Find &Next    F3",                    IDM_FINDNEXT
        MENUITEM "R&eplace...    Ctrl+H",       IDM_REPLACE
    END

    POPUP "&Help"
    BEGIN
        MENUITEM "&About CE Pad",                    IDM_ABOUT
    END
END

Menu-template resources can be loaded explicitly or assigned as the default menu for a window class. To load a menu explicitly, call the LoadMenu function. To assign a menu to a window class, assign the name of the menu-template resource to the lpszMenuName member of the WNDCLASS structure used to register the class.


Using Menu Creation Functions

You use menu creation function when you want to create or change a menu at run time. To create an empty menu bar, use the CreateMenu function; to create an empty menu, use the CreatePopupMenu function. To add items to a menu, use the AppendMenu and InsertMenu functions.

When a menu contains more items than will fit in one column, the menu is truncated unless you force the a line break. You can cause a column break to occur at a specific item in a menu by assigning the MFT_MENUBREAK type flag to the item. Windows CE places that item and all subsequent items in a new column. You can also assign the MFT_MENUBARBREAK type flag to the item, which has the same effect as MFT_MENUBREAK, except that a vertical line appears between the new and existing columns.

To display a shortcut menu, use the TrackPopupMenuEx function. Shortcut menus, also called floating pop-up menus, or context menus, are typically displayed when the WM_CONTEXTMENU message is processed. The older TrackPopupMenu function is supported, but new applications should use the TrackPopupMenuEx function.

If a menu is assigned to a window and that window is destroyed, Windows CE automatically destroys the menu, freeing the menu handle and the memory occupied by the menu. Windows CE does not automatically destroy a menu not assigned to a window. An application must destroy the unassigned menu by calling the DestroyMenu function.


Setting Menu Item Attributes

Menu items possess attributes that affect their appearance. For example, a menu item can be checked or unchecked, mutually exclusive, or dimmed. You can change other menu attributes as well by creating an owner-drawn menu item. Each menu item in a command bar or menu has a unique position value. The leftmost item in a command bar, or the top item in a menu, has position zero. The position value is incremented for subsequent menu items. Windows CE assigns a position value to all menu items, including separators. When calling a menu function that modifies or retrieves information about a specific menu item, specify the item using either its handle or its position.

A check mark attribute controls whether a menu item is checked. Applications check or uncheck a menu item to indicate whether an option is in effect. For example, suppose that an application has a toolbar that the user can hide or display by using a Toolbar command on a menu. When the toolbar is hidden, the Toolbar menu item is unchecked. When the user chooses the command, the application checks the menu item and shows the toolbar. Windows CE displays a bitmap next to checked menu items to indicate their checked state; it does not display a bitmap next to unchecked items. Only menu items in a menu can be checked. Items appearing in a command bar cannot be checked. To set a menu item check mark attribute, call the CheckMenuItem function.

Sometimes, a group of menu items corresponds to a set of mutually exclusive options. In this case, indicate the selected option by using a checked radio menu item — analogous to a radio button control. Checked radio items are displayed with a bullet bitmap instead of a check mark bitmap. To check a menu item and make it a radio item, use the CheckMenuRadioItem function.

Another menu item attribute you can change is whether or not a menu is enabled. When a menu item is not available, the item should be dimmed. Dimmed menu items cannot be chosen. You can use a dimmed item when an action is not appropriate. For example, you can dim the Print command in the File menu when the system does not have a printer installed. A menu item can be enabled or dimmed by using the EnableMenuItem function. To determine whether a menu item is enabled or dimmed, use the GetMenuItemInfo function.

You can control a menu item’s appearance by using an owner-drawn item. Owner-drawn items require an application to draw selected, checked, and unchecked states. For example, if an application provides a font menu, it can draw each menu item by using the corresponding font; the item for Roman will be drawn in Roman, the item for Italic will be drawn in Italic, and so on.

Windows CE handles owner-drawn menu items differently from Windows-based desktop platforms. In some respects, it treats an owner-drawn item as any other menu item. On other Windows-based platforms, the device context is initialized to its default state. On Windows CE, the device context is initialized to the dimmed or highlighted status of the current item. Also, unlike other Windows-based platforms, Windows CE automatically highlights an owner-drawn menu item when it has the keyboard focus.


Creating Keyboard Accelerators

A keyboard accelerator, also known as a shortcut key, is a keystroke, or combination of keystrokes, that generates a WM_COMMAND message. Keyboard accelerators are often used as shortcuts for commonly used menu commands, but you can also use them to generate commands that have no equivalent menu items. Include keyboard accelerators for any common or frequent actions, and provide support for the common shortcut keys where they apply.

You can use an ASCII character code or a virtual-key code to define the accelerator. A virtual key is a device-independent value that identifies the purpose of a keystroke as interpreted by the Windows keyboard device driver. An ASCII character code makes the accelerator case-sensitive. The ASCII “C” character can define the accelerator as alt+c rather than alt+c. Because accelerators do not need to be case-sensitive, most applications use virtual-key codes for accelerators rather than ASCII character codes.

If an application defines an accelerator also defined in the system accelerator table, the application-defined accelerator overrides the system accelerator, but only within the application context. Avoid this, because it prevents the system accelerator from performing its standard role in the Windows CE user interface.

To create an accelerator table

  1. Use a resource compiler to define an accelerator table resource and to add it your executable file.

    An accelerator table consists of an array of ACCEL data structures, each of which defines an individual accelerator.

  2. Call the LoadAccelerators function at run time to load the accelerator table and to retrieve the handle of the accelerator table.
  3. Pass a handle to the accelerator table to the TranslateAccelerator function to activate the accelerator table.

You can also create an accelerator table for an application at run time by passing an array of ACCEL structures to the CreateAcceleratorTable function. This method supports user-defined accelerators in the application. CreateAcceleratorTable creates an accelerator table that must be destroyed before an application closes. Call the DestroyAcceleratorTable function to destroy the accelerator table.


Defining an Accelerator Table

The following code example shows the syntax for accelerator table definitions.

acctablename ACCELERATORS [optional-statements] { event, idvalue, [type] [options] . . . }

When using the ACCELERATORS statement in your resource-definition file, assign a unique name or resource identifier to the accelerator table. Windows CE uses the identifier to load the resource at run time.

Each accelerator you define requires a separate entry in the accelerator table. In each entry, you define the keystroke that generates the accelerator and the accelerator identifier. The keystroke is either an ASCII character code or a virtual-key code. Also specify if the keystroke must be used in some combination with the alt, shift, or ctrl key.

An ASCII keystroke is specified either by enclosing the ASCII character in double quotation marks or by using the integer value of the character in combination with the ASCII flag. The following code examples show how to define ASCII accelerators.

"A",   ID_ACCEL1         ; SHIFT+A
65,    ID_ACCEL2, ASCII  ; SHIFT+A

A keystroke that generates a virtual-key code is specified differently depending on whether the keystroke is an alphanumeric key or a non-alphanumeric key. For an alphanumeric key, the key’s letter or number, enclosed in double quotation marks, is combined with the VIRTKEY flag. For a non-alphanumeric key, the Windows CE virtual-key code for the specific key is combined with the VIRTKEY flag. The following code examples show how to define virtual-key code accelerators.

"a",         ID_ACCEL3, VIRTKEY   ; A (caps-lock on) or a
VK_INSERT,   ID_ACCEL4, VIRTKEY   ; INSERT key

If you want the user to press the alt, shift, or ctrl keys in some combination with the accelerator keystroke, specify the ALT, SHIFT, and CONTROL flags in the accelerator definition. The following code examples show possible combinations.

"B",   ID_ACCEL5, ALT                     ; ALT_SHIFT+B
"I",   ID_ACCEL6, CONTROL, VIRTKEY        ; CTRL+I
VK_F5, ID_ACCEL7, CONTROL, ALT, VIRTKEY   ; CTRL+ALT+F5

You can also set the NOINVERT flag in the options parameter. NOINVERT prevents the selected menu item from being highlighted when its accelerator key is pressed. This is useful when defining accelerators for actions such as scrolling that do not correspond to a menu item.


Loading and Activating an Accelerator Table

An application loads an accelerator-table resource by calling the LoadAccelerators function and specifying the instance handle to the application whose executable file contains the resource and the name or identifier of the resource. LoadAccelerators loads the specified accelerator table into memory and returns the handle to the accelerator table.

An application can load an accelerator-table resource at any time. Usually, a single-threaded application loads its accelerator table before entering its main message loop. An application that uses multiple threads typically loads the accelerator-table resource for a thread before entering the message loop for the thread. An application or thread might also use multiple accelerator tables, each associated with a particular application window. This type of application loads the accelerator table for the window each time the user activates the window.

Windows CE maintains accelerator tables for each application. An application can define any number of accelerator tables for use with its own windows. A unique 32-bit handle, HACCEL, identifies each table. However, only one accelerator table can be active at a time for a specified thread.

To activate an accelerator table, call the TranslateAccelerator function in the message loop associated with the thread message queue to process accelerator keystrokes for a specified thread. The handle of the accelerator table passed to the TranslateAccelerator function determines which accelerator table is active for a thread. This function also monitors keyboard input to the message queue, checking for key combinations that match an entry in the accelerator table. When TranslateAccelerator finds a match, it translates the keyboard input, that is, the WM_KEYUP and WM_KEYDOWN messages, into a WM_COMMAND or WM_SYSCOMMAND message. It then sends the message to the window procedure of the specified window. The WM_COMMAND message includes the identifier of the accelerator that caused TranslateAccelerator to generate the message. The window procedure examines the identifier to determine the source of the message and then processes the message accordingly.

The following code example shows how to call TranslateAccelerator from within a message loop.

while (GetMessage (&msg, NULL, 0, 0))
{
  if (!TranslateAccelerator (
                  g_hwndMain,   // Handle to destination window.
                  hAccel,       // Handle to accelerator table.
                  &msg))        // Address of message data.
  {
    TranslateMessage (&msg);
    DispatchMessage (&msg);
  }
}

Note Unlike Windows-based desktop platforms, Windows CE does not maintain a system-wide accelerator table that applies to all applications.

To change the active accelerator table, pass a different accelerator-table handle to TranslateAccelerator.


Creating Dialog Boxes

A dialog box is a temporary window that contains controls. A dialog box is used to display status information and to prompt the user for input. Windows CE supports two types of dialog boxes: modal and modeless. A modal dialog box requires the user to supply information or dismiss the dialog box before enabling the application to continue. Applications use modal dialog boxes in conjunction with commands that require additional information before they can proceed. A modeless dialog box enables the user to supply information and return to a previous task without closing the dialog box. Modal dialog boxes are simpler to manage than their modeless counterparts because they are created and destroyed by calling a single function.

To create either a modal or modeless dialog box, you must define a dialog box template to describe the dialog box style and content. The dialog box template is a binary description of the dialog box and the controls that it contains. You must also supply a dialog box procedure to execute tasks. You can create this template as a resource that you can load from your executable file.

To define a dialog box template

  1. Define an identifier for the dialog box in a header file.
  2. Define a dialog box in the application resource file using the DIALOG statement. The statement defines the position and dimensions of the dialog box on the screen as well as the dialog box style, and has the following parameters.
Parameter
Description
Use
nameID | Dialog box name | Specifies a unique identifier for the dialog box
x | x coordinate | Specifies the x coordinate of the upper-left corner of the dialog box
y | y coordinate | Specifies the y coordinate of the upper-left corner of the dialog box
Width | Dialog width | Specifies the width of the dialog box
Height | Dialog height | Specifies the height of the dialog box
Option statements | Dialog box options | Specifies one or more features of the dialog box.
Control statements | Controls associated with the dialog box | Specifies one or more controls using the appropriate control statement.

For a listing of option statements, see the DIALOG statement in the Windows CE Reference. For a listing of control statements, see “Common Control Parameters” in the Windows CE Reference.

The following code example demonstrates how to use the DIALOG statement to define a dialog box template.

#include <windows.h>

#define IDD_REPLACE                     106
#define IDC_FINDWHAT                    1000
#define IDC_REPLACE                     1001
#define IDC_BTNREPLACE                  1008
#define IDC_BTNREPLACEALL               1009
#define IDC_BTNFINDNEXT                 1010
#define IDC_STATIC1                     1011
#define IDC_STATIC2                     1012

IDD_REPLACE DIALOG DISCARDABLE  0, 0, 191, 73
STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Replace"
FONT 8, "MS Sans Serif"
BEGIN
    EDITTEXT        IDC_FINDWHAT,55,4,128,14,ES_AUTOHSCROLL
    EDITTEXT        IDC_REPLACE,55,21,128,14,ES_AUTOHSCROLL
    PUSHBUTTON      "&Find Next",IDC_BTNFINDNEXT,18,38,71,14
    PUSHBUTTON      "&Replace",IDC_BTNREPLACE,18,55,71,14
    PUSHBUTTON      "Replace &All",IDC_BTNREPLACEALL,101,38,71,14
    PUSHBUTTON      "Cancel",IDCANCEL,101,55,71,14
    LTEXT           "Fi&nd what:",IDC_STATIC1,7,4,43,8
    LTEXT           "Re&place with:",IDC_STATIC2,7,21,43,8
END

The dialog box procedure is an application-defined callback function that the system calls when it has input for the dialog box or tasks for the dialog box to execute. A dialog box procedure is similar to a window procedure in that the system sends messages to the procedure when it has data or tasks to execute. Although a dialog box procedure is similar to a window procedure, it does not have the same responsibilities. Unlike a window procedure, a dialog box procedure never calls the DefWindowProc function. Rather, it returns TRUE if it processes a message or FALSE if it does not.

The following code example shows the form of a dialog box procedure.

BOOL CALLBACK ReplaceDialogProc (
                    HWND hwndDlg,   // Handle to dialog box.
                    UINT uMsg,      // Message.
                    WPARAM wParam,  // First message parameter.
                    LPARAM lParam)  // Second message parameter.
{
  switch(uMsg)
  {
    case WM_INITDIALOG:
      // Insert code here to put the string (to find and replace with)
      // into the their edit controls.
      // ...
      return TRUE;

    case WM_COMMAND:
      switch (LOWORD(wParam))
      {
        case IDC_BTNFINDNEXT:
          // Insert code here to handle the case of pushing
          // "Find Next" button.
          // ...
          break;

        case IDC_BTNREPLACE:
          // Insert code here to handle the case of pushing
          // "Replace" button.
          // ...
          break;

        case IDC_BTNREPLACEALL:
          // Insert code here to handle the case of pushing
          // "Replace All" button.
          // ...
          break;

        case IDCANCEL:
          EndDialog(hwndDlg, 0);
          break;
      }
      return TRUE;
  }
  return FALSE;
}

Here, the hwndDlg parameter receives the window handle of the dialog box.

Most dialog box procedures process the WM_INITDIALOG message and the WM_COMMAND messages sent by the controls, but process few if any other messages. If a dialog box procedure does not process a message, it must return FALSE to direct the system to process the messages internally. The only exception to this rule is the WM_INITDIALOG message. Typically, you initialize the dialog box and its contents while processing the WM_INITDIALOG message. The most common task you perform using the WM_INITDIALOG message is to initialize the controls to reflect the current dialog box settings. Another common task is to center a dialog box on the screen or within its owner window. A useful task for some dialog boxes is to set the input focus to a given control rather than accept the default input focus. The dialog box procedure must return TRUE to direct the system to process the WM_INITDIALOG message.

Typically, you create a dialog box by calling either the DialogBox or CreateDialog function. DialogBox creates a modal dialog box; CreateDialog creates a modeless dialog box. When calling these functions, you must specify the identifier or name of a dialog box template resource and the address of the dialog box procedure. The DialogBox and CreateDialog functions load the specified dialog box template, display the dialog box, and process all user input until the user closes the dialog box.

The following code example shows how to create a modal dialog box.

DialogBox (
      g_hInst,                        // Handle to application instance
      MAKEINTRESOURCE (IDD_REPLACE),  // Identifies dialog box template
      g_hwndMain,                     // Handle to owner window
      (DLGPROC) ReplaceDialogProc);   // Pointer to dialog box procedure

The following code example shows how to create a modeless dialog box.

CreateDialog (
      g_hInst,                        // Handle to application instance
      MAKEINTRESOURCE (IDD_REPLACE),  // Identifies dialog box template
      g_hwndMain,                     // Handle to owner window
      (DLGPROC) ReplaceDialogProc);   // Pointer to dialog box procedure

Dialog boxes usually belong to a predefined, exclusive window class. The system uses this window class and its corresponding window procedure for both modal and modeless dialog boxes. When the function is called, it creates the window for the dialog box, as well as the windows for the controls in the dialog box, and then sends selected messages to the dialog box procedure. While the dialog box is visible, the predefined window procedure manages all messages, processing some messages and passing others to the dialog box procedure so that the procedure can execute tasks. You do not have direct access to the predefined window class or window procedure, but you can use the dialog box template and dialog box procedure to modify the style and behavior of a dialog box.

The following table shows dialog box types.

Dialog box type
Description
Application-defined dialog box | Helps a user perform application-specific tasks
Common dialog box | Provides a familiar way for users to perform common application tasks
Message box | Notifies a user of an event or situation and offers limited responses
Property sheet, a collection of tabbed dialog boxes | Provides a convenient way to view and modify object properties


Application-Defined Dialog Boxes

An application-defined dialog box is a child window that you design to suit the needs of your application. You can use any format for, or use any kind of control in a dialog box. The following screen shot shows an application-defined dialog box.

In Windows CE, all dialog boxes are control parents. They are also recursive. This means that if a dialog box has a child dialog box when a user tabs through the parent dialog box, the dialog box manager tabs into the child dialog box as well. If a dialog box is outside the visible area of the screen, Windows CE does not automatically reposition it.

If a user presses ALT+H while the dialog box has the input focus, the system posts a WM_HELP message to the dialog box procedure. Respond to this message by displaying context-sensitive Help for the dialog box.

Sometimes it is necessary for a dialog box to appear on top of all other windows. For example, under low memory conditions, the System Out of Memory Dialog Box will send a WM_CLOSE message to an application. If the application is not in the foreground, any dialog box it displays will be hidden behind the current foreground window, unless you create the dialog box with the DS__SETFOREGROUND style. Because putting the dialog box in the foreground will not bring the application’s main window forward, put in the dialog box any information that the user may need to decide what action to take.

In Windows CE, dialog boxes have the WS_POPUP style by default. If you want to use the WS_CHILD style, specify it in the style member of the DLGTEMPLATE structure you pass in the lpTemplate parameter to any of these functions. You can also specify the DS_SETFOREGROUND or DS_CENTER styles.

For a list of dialog box styles supported by Windows CE, see Window and Control Styles.


Common Dialog Boxes

A common dialog box is a system-defined dialog box that standardizes how users perform complex operations common to most applications. Windows CE supports the Color, Open, Save As, and Print common dialog boxes. The following screen shot shows a Print dialog box.

The following table describes each of the common dialog boxes supported in Windows CE.

Common Dialog Box
Description
Color | Provides users with a way to select a color from a set of custom colors or from a set of basic colors determined by the display driver
Open | Provides users with a way to select a file to open
Save As | Provides users with a way to save a file under another name
Print | Provides users with a way to select print options

Users must print the entire document or the currently selected portion and can print only one copy at a time. The settings in the Print dialog box initialize to the default printer. If the user has never used the Print dialog box before, the first printer registered in the registry is the default. After that, the last printer the user selected is the default. You can set the widths and minimum widths of the left, top, right, and bottom margins of the printed page by including values for the rcMargin and rcMinMargin members of the PRINTDLG structure.

Common dialog boxes are centered vertically and horizontally on the screen and are not movable. They always have the Help button displayed.


Message Boxes

A message box is a special kind of modal dialog box an application uses to display messages and prompt for input. A message box typically contains a text message and one or more predefined buttons. The following screen shot shows a message box.

To create a message box, call the MessageBox function specifying the text and the number and types of buttons to display; because Windows CE controls the message box creation and management, you do not provide a dialog box template and dialog box procedure. Windows CE creates its own template based on the text and buttons specified for the message box and supplies its own dialog box procedure.

As with dialog boxes, sometimes it is necessary for a message box to appear on top of all other windows. In particular, under low memory conditions, the System Out of Memory Dialog Box sends a WM_CLOSE message to an application. If the application is not in the foreground, any message box it puts up is hidden behind the current foreground window unless you create the message box with the MB_SETFOREGROUND style. Because putting the message box in the foreground will not bring the application’s main window forward, put any information in the message box that the user may need to decide what action to take.

The MessageBeep function, generally used with message boxes, plays a waveform sound. The waveform sound for each sound type is identified by an entry in the sounds section of the registry.

For a list of message box styles that are supported by Windows CE, see Window and Control Styles.


Creating a Caret

A caret is a flashing line or block in the window client area that indicates the place where text or graphics will be inserted. Windows CE provides one caret per message queue. You should create a caret only when its associated window has keyboard focus or is active. You should destroy the caret before the window loses keyboard focus or becomes inactive. Once you create a caret, you can change how frequently a caret blinks, modify the caret position within a window, or temporarily remove a caret from view by hiding it. An application should create and display a caret while processing the WM_SETFOCUS message. Windows CE sends the WM_SETFOCUS message to a window when it receives keyboard focus.

To create and display a caret in a window

  1. Call the CreateCaret function when the window receives focus. Windows CE formats a caret by inverting the pixel color within the rectangle specified by the caret’s position, width, and height.
  2. Set the caret position by calling the SetCaretPos function.
  3. Make the caret visible by calling the ShowCaret function. When the caret appears, it begins flashing.

The following screen shot shows a caret as it appears in text.

The elapsed time, in milliseconds, required to invert the caret is called the blink time. The flash time is the elapsed time, in milliseconds, required to display, invert, and restore the caret’s display. The flash time of a caret is twice as much as the blink time. The caret will blink as long as the thread that owns the message queue processes messages. The user can set the blink time of the caret using Control Panel, and applications should respect the settings that the user has chosen. An application can determine the caret blink time by using the GetCaretBlinkTime function. If you are writing an application that enables the user to adjust the blink time, such as a Control Panel applet, use the SetCaretBlinkTime function to set the blink time rate to a specified number of milliseconds.

To determine the caret position, use the GetCaretPos function. An application can move a caret in a window by using the SetCaretPos function. A window can move a caret only if it already owns the caret. SetCaretPos can move the caret whether or not it is visible.

You can temporarily remove a caret by hiding it, or you can permanently remove the caret by destroying it. To hide the caret, use the HideCaret function. This is useful when the application must redraw the screen while processing a message, but must keep the caret out of view. When the application finishes drawing, it can display the caret again by using the ShowCaret function. Hiding the caret does not destroy its shape or invalidate the insertion point. Hiding the caret is cumulative; that is, if the application calls HideCaret five times, it must also call ShowCaret five times before the caret will reappear.

To remove the caret from the screen and destroy its shape, use the DestroyCaret function. DestroyCaret destroys the caret only if the window involved in the current task owns the caret.


Creating a Cursor

A cursor is a small bit image that reflects the position of a pointing device.

Because standard cursors are predefined, it is not necessary to create them. To use a standard cursor, an application retrieves a cursor handle by calling the LoadCursor function. A cursor handle is a unique value of the HCURSOR type that identifies a standard or custom cursor.

The following code example shows the syntax for the LoadCursor function.

HCURSOR LoadCursor(
   HINSTANCE hInstance, // Handle to application instance
   LPCTSTR lpCursorName // Name string or cursor resource identifier
);

Here, hInstance is a handle to an instance of the module whose executable file contains the cursor to be loaded, and lpCursorName is a pointer to the name of the cursor to be loaded. It can also point to a resource identifier. To use a predefined cursor, the application must set hInstance to NULL and lpCursorName to one of the predefined cursor values.

Windows CE-based platforms implement cursors in different ways depending on the platform configuration. For example, on many Windows CE-based platforms, users interact with applications by tapping the stylus on the screen; because there is no mouse, there is no need for a cursor to indicate the current mouse position. Target platforms not requiring mouse support typically implement Iconcurs.dll. This component enables you to specify only the wait cursor when calling the LoadCursor function. Applications should display the wait cursor when executing a command that renders the current window or the system unresponsive to user input. To establish the shape of a wait cursor, you must call the SetCursor function in conjunction with the LoadCursor function. The following code example shows how to establish the shape of a wait cursor.

SetCursor(LoadCursor(NULL, IDC_WAIT));

Target platforms that support mouse cursors typically include Mcursor.dll. This component implements cursors similar to Windows-based desktop platforms; all standard cursors, except color cursors, are available when calling the LoadCursor function. Windows CE also supports custom cursors.

To create a custom cursor

  1. Draw the cursor using a graphics application.
  2. Include the cursor as a resource in the application resource-definition file.

    Using a cursor resource avoids device dependence, simplifies localization, and enables applications to share cursor designs.

  3. Call LoadCursor at run time to retrieve the cursor handle.

    Cursor resources contain data for several different display devices. The LoadCursor function automatically selects the most appropriate data for the current display device. To load a cursor directly from a .cur or .ani file, use the LoadCursorFromFile function instead of the LoadCursor function.

Once you create and load a cursor, you can hide and redisplay the cursor, without changing the cursor design, by using the ShowCursor function. This function uses an internal counter to determine when to hide or display the cursor. An attempt to show the cursor increments the counter; an attempt to hide the cursor decrements the counter. The cursor is visible only if this counter is greater than or equal to zero.

Additionally, you can change the design of the cursor by using the SetCursor function and specifying a different cursor handle.


Creating Icons, Bitmaps, Images, and Strings

An icon is an image used to identify an application, file, or other object. It consists of a bit image combined with a mask. An application icon always appears on the taskbar while the application is running, and it can be used to recover the application main window when another window has the foreground. The icon can also be used to identify the application in the Windows CE Explorer. Every application should register both 16 x 16 pixel and 32 x 32 pixel icons for its main executable file and the types of files it stores in the file system.

A bitmap is a graphics image that you can include in an application. Unlike icons, which have a fixed size, you determine the bitmap size. Both icons and bitmaps must be defined in a resource file.

To create an icon

  1. Draw the icon using a graphics application.
  2. Include the icon as a resource in the application resource-definition file.
  3. Call the WM_SETICON message to associate an icon with a window class.

    Icons are associated with window classes rather than with individual windows.

  4. Call the WM_GETICON message to retrieve the handle of the icon associated with a window class.

Note Windows CE does not support any of the standard predefined icons (IDI_*) that Windows-based desktop platforms support.

To create a bitmap

  1. Draw the bitmap using a graphics application.
  2. Include the bitmap as a resource in the application resource-definition file.
  3. Call the LoadBitmap function to initialize the bitmap.

    The bitmap you create with this function will be read-only, because Windows CE does not copy the bitmap into RAM, as Windows-based desktop platforms do.

  4. Call the SelectObject function to select the bitmap into a device context. This enables you to display the bitmap.

    When you select the bitmap into a device context, you cannot modify the device context—for example, by drawing text into it—because that would require the ability to write to the bitmap.

Images and strings are created similarly to icons and bitmaps. Both are resources that must be defined in a resource file. The following code example shows how to define a resource for an icon, bitmap, cursor, and string.

#include "windows.h"

#define IDB_BITMAP                      101
#define IDC_CURSOR                      102
#define IDI_CEPADICON                   103

IDB_CEPADBITMAP     BITMAP  DISCARDABLE     "CePad.bmp"
IDC_CEPADCURSOR     CURSOR  DISCARDABLE     "CePad.cur"
IDI_CEPADICON       ICON    DISCARDABLE     "CePad.ico"

STRINGTABLE DISCARDABLE
BEGIN
  1                 "CePad"
  2                 "CePad Application"
END

When adding an image or string to an application, call the LoadImage function to load an image and call the LoadString function to load a string. Windows CE does not support stretching and shrinking of images or any loading options other than LR_DEFAULTCOLOR. Windows CE supports only Unicode strings.


Creating Timers

A timer is a system resource that can notify an application at regular intervals. An application associates a timer with a window and sets the timer for a specific time-out period. Each time the specified interval, or time-out value, for a specified timer elapses, the system uses a WM_TIMER message to notify the window associated with the timer; because the accuracy of a timer depends on the system clock rate and how often the application retrieves messages from the message queue, the time-out value is only approximate. The smallest possible interval that a timer can measure is the system tick interval.

To create a timer, call the SetTimer function. The timer can be associated with a particular window or with just the thread. If you associate the timer with a window, then message loop processing will cause the WM_TIMER message to be dispatched to the window’s window procedure. If you do not associate the timer with a window, you must design the message loop to recognize and handle the WM_TIMER message.

If the call to SetTimer includes a TimerProc callback function, the procedure is called when the timer expires. This call is done inside the GetMessage or PeekMessage function. This means that a thread must be executing a message loop to service a timer, even if you are using a timer callback procedure.

A new timer starts timing its interval as soon as it is created. An application can change a timer’s time-out value by calling the SetTimer function, and it can destroy a timer by calling the KillTimer function. To use system resources efficiently, applications should destroy unnecessary timers.

You can use the timer and window identifiers to identify timers associated with a window. You can identify timers not associated with a particular window by using the identifier returned by the SetTimer call.

Timer messages have low priority in the message queue. Although you know that the window associated with a timer is notified sometime after the timer interval expires, you cannot know the exact time it will receive the notification.

Timers expire at regular intervals, but a timer that expires multiple times before being serviced does not generate multiple WM_TIMER messages.


Creating Controls

This chapter explains how to add window, common, and Windows CE-specific controls to windows and dialog boxes. It also explains how to handle control notification messages. The window controls discussed in this chapter include buttons, combo boxes, edit controls, list boxes, scroll bars, and static controls.

A control is a child window that an application uses in conjunction with another window to perform simple I/O tasks. Windows CE defines two basic kinds of controls: window controls and common controls. Window controls all send WM_COMMAND messages. Common controls generally send WM_NOTIFY messages, though a few send WM_COMMAND messages.

Windows CE also supports two Windows CE-specific controls: an HTML viewer control and a Rich Ink control. These controls are neither window nor common controls. The HTML viewer control provides a simple interface for rendering HTML text, displaying embedded images, and notifying the application of user events. The Rich Ink control enables a user to write and draw on a touch-sensitive screen with a pointing device.

Controls are most often placed within dialog boxes, but can also be placed directly on the surface of a normal window client area. Each control has styles or attributes that affect its appearance and behavior. When you create a control, you can apply the one or more styles to the control. For a complete listing of all supported Windows CE control styles, see Window and Control Styles


Working with Window Controls

A window control is a predefined child window that enables a user to make selections, carry out commands, and perform I/O tasks. You can place a window control within a dialog box or in the client area of a normal window. Controls placed within dialog boxes provide the user with the means to type text, select options, and direct a dialog box to complete its action. Controls placed in normal windows provide a variety of services, such as choosing commands, scrolling, and viewing and editing text.

Although you can create your own window controls, Windows CE has several predefined window classes that you can use to add a standard window control to your application. The following table shows predefined window classes supported by Windows CE.

Window class
Description
BUTTON | Creates a button control, which notifies the parent window when the user selects the button
COMBOBOX | Creates a combo box—a combination of list box and edit control—that enables the user to select and edit items
EDIT | Creates an edit control, which lets the user view and edit text.
LISTBOX | Creates a list box, which displays a list from which the user can select one or more items
SCROLLBAR | Creates a scroll bar control, which enables the user to scroll horizontally and vertically within a window
STATIC | Creates a static control, which often acts as a label for another control; static controls can display both text and images, such as icons

Because window controls are child windows, create a window control by calling the CreateWindowsEx function. This creates a single control in a normal window. To create control in a dialog box, use the dialog box template contained in your application resource file. By using a resource file, you can create multiple controls simultaneously. For more information on resources and resource files, see Using Resources.

Most compilers come with automated tools, called resource editors, to create resources. Using a resource editor is probably the most accurate and efficient way to add a control to a dialog box. However, because resource editors vary, providing instruction for using a resource editor is beyond the scope of this book. For more information about your compiler’s resource editor, see your compiler User’s Guide

To use a window control, you must include either the Windows.h or the Winuser.h header file in your application. Windows.h includes Winuser.h.

To create a window control in a normal window

  1. Define an identifier for the control in the application header file.

    A control identifier is a value that uniquely identifies a control sending the message. In Windows CE, control identifiers are only valid for child windows.

  2. Call the CreateWindowEx function and specify the following parameters.
Parameter
Description
Use
DWORD dwExStyle | Extended window style | Specify an extended window style.
LPCTSTR lpClassName | Class name | Specify a predefined window class. For example, to create a push button, specify “button.”
LPCTSTR lpWindowName | Window text | Specify the text you want to appear on the control.
DWORD dwStyle | Window style | Specify a control style. Each predefined window class has a corresponding set of control styles that enable an application to vary the appearance and behavior of the controls it creates. For example, the BUTTON class supports styles to create a push button, radio button, check box, or group box.
int x | x coordinate | Specify the x coordinate of the upper left corner of the control relative to the upper left corner of the parent window client area.
int y | y coordinate | Specify the upper left corner y coordinate of the control relative to the upper left corner of the parent window client area.
int nWidth | Width | Specify the control width.
int nHeight | Height | Specify the control height.
HWND hWndParent | Parent window | Specify the handle to the parent window, hWnd.
HMENU hMenu | Child window identifier | Specify the control identifier.
HINSTANCE hInstance | Instance handle | Specify the application or module to be associated with the window.
LPVOID lpParam | Extra parameters | Specify NULL when creating a control.

Once you call CreateWindowEx, Windows CE handles all repainting tasks. It also destroys all controls upon the application’s termination. The following code example shows how to add a control to a normal window using the CreateWindowEx function.

DWORD dwStyle = WS_VISIBLE | WS_CHILD | TVS_HASLINES | TVS_LINESATROOT |
                TVS_HASBUTTONS;

hwndTreeView = CreateWindow (
      WC_TREEVIEW,          // Class name
      TEXT ("Tree View"),   // Window name
      dwStyle,              // Window style
      0,                    // x coordinate of the upper left corner
      0,                    // y coordinate of the upper left corner
      CW_USEDEFAULT,        // The width of the treeview control window
      CW_USEDEFAULT,        // The height of the treeview control window
      hwnd,                 // Window handle of parent window
      (HMENU)IDC_TREEVIEW,  // The treeview control identifier
      hInst,                // The instance handle
      NULL);                // Specify NULL for this parameter when
                            // creating a control

To create a control in a dialog box

  1. Define an identifier for each control in a header file.

    A control identifier is a unique number that the application uses to identify the control sending the message. In Windows CE, control identifiers are only valid for child windows.

  2. Define a dialog box in your application’s resource file using the DIALOG statement. The statement defines the position and dimensions of the dialog box on the screen as well as the dialog box style, and has the following parameters.
Parameter
Description
Use
nameID | Dialog box name | Specify a unique identifier for the dialog box.
x | x coordinate | Specify the x coordinate of the upper left corner of the dialog box.
y | y coordinate | Specify the y coordinate of the upper left corner of the dialog box.
Width | Dialog width | Specify the width of the dialog box.
Height | Dialog height | Specify the height of the dialog box.
Option statements | Dialog box options | Specify one or more features of the dialog box. For example, use CAPTION to add a title to the dialog box or DISCARDABLE to remove the dialog box from memory when not in use. For a listing of option statements, see the DIALOG statement in the Windows CE Reference.
Control statements | Controls associated with the dialog box | Specify one or more controls using the appropriate CONTROL statement.

  3. Call either the DialogBox function or the CreateDialog function and specify the identifier or name of the dialog box template and the address of the dialog box procedure.

    DialogBox creates a modal dialog and CreateDialog creates a modeless dialog. For more information on creating dialog boxes, see Working with Windows and Messages.

The following code example demonstrates how to create a push button and static text control in a dialog box.

#include <windows.h>

#define IDD_ABOUT               103
#define IDC_STATIC              -1

IDD_ABOUT DIALOG DISCARDABLE  0, 0, 132, 55
STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "About CE Pad"
FONT 8, "MS Sans Serif"
BEGIN
  DEFPUSHBUTTON   "OK",IDOK,39,34,50,14
  CTEXT           "Microsoft Window CE",IDC_STATIC,7,7,118,8
  CTEXT           "CePad Sample Application",IDC_STATIC,7,19,118,8
END


Handling Notification Messages

All window controls respond to user input or changes to the control by sending a notification message to its parent window. A notification message is a WM_COMMAND message that includes a control identifier and a notification code identifying the nature of the event. An application must trap these notification messages and react to them.

The following code example shows one method of trapping a WM_COMMAND message.

BOOL CALLBACK AboutDialogProc (
                        HWND hwndDlg,     // Handle to the dialog box
                        UINT uMsg,        // Message
                        WPARAM wParam,    // First message parameter
                        LPARAM lParam)    // Second message parameter
{
  switch (uMsg)
  {
    case WM_INITDIALOG:
      return TRUE;

    case WM_COMMAND:
      switch (LOWORD (wParam))
      {
        case IDOK:
          EndDialog (hwndDlg, IDOK);
          return TRUE;

        case IDCANCEL:
          EndDialog (hwndDlg, IDCANCEL);
          return TRUE;
      }
      break;
  }
  return FALSE;
}

Some window controls receive messages as well as generate them. Typically, a window procedure sends a message to a control directing it to execute a task. The control processes the message and carries out the requested action. Windows CE has several predefined messages, such as WM_GETTEXT and WM_GETDLGCODE, that it sends to controls. These messages typically correspond to window-management functions that carry out actions on windows. The window procedure for an application-defined control processes any predefined control message that affects the operation of the control. The following table describes these messages.

Message
Recommendation
WM_GETDLGCODE | Process if the control uses the ENTER, ESC, TAB, or arrow keys. The IsDialogMessage function sends this message to controls in a dialog box to determine whether to process the keys or pass them to the control.
WM_GETFONT | Process if the WM_SETFONT message is also processed.
WM_GETTEXT | Process if the control text is not the same as the title specified by the CreateWindowEx function.
WM_GETTEXTLENGTH | Process if the control text is not the same as the title specified by the CreateWindowEx function.
WM_KILLFOCUS | Process if the control displays a caret, a focus rectangle, or another item to indicate that it has the input focus.
WM_SETFOCUS | Process if the control displays a caret, a focus rectangle, or another item to indicate that it has the input focus.
WM_SETTEXT | Process if the control text is not the same as the title specified by the CreateWindowEx function.
WM_SETFONT | Process if the control displays text. Windows CE sends this message when creating a dialog box that has the DS_SETFONT style.

You can also send messages to a control by calling the SendMessage function. One control that calls the SendMessage function to receive messages is the button.


Creating a Button

A button is a window control that a user can turn on or off to provide input to an application. Buttons can be used alone or in groups and can appear with or without a label. Buttons belong to the BUTTON window class.

Windows CE provides four kinds of buttons: check boxes, push buttons, radio buttons, and group boxes. Each button type has one or more styles that affect its appearance, behavior, or both. For a list of supported button styles, see Window and Control Styles

A check box contains one or more items that appear checked when selected. More than one item in a check box can be selected at one time. Applications display check boxes in a group box to enable the user to choose from a set of related, but independent options. When the user selects a check box of any style, the check box receives the keyboard focus from Windows CE, which sends the check box parent window a WM_COMMAND message containing the BN_CLICKED notification code. The parent window does not acknowledge this message if sent from an automatic check box or automatic three-state check box, because Windows CE sets the check state for those styles. The parent window must acknowledge the message if sent from an application-defined check box or three-state check box because the parent window, not Windows CE, is responsible for setting the check state for those styles. Regardless of the check box style, Windows CE repaints the check box once its state is changed.

Check box

To create a check box using the CreateWindow function

  1. Specify the BUTTON window class in the lpClassName parameter of the CreateWindow or CreateWindowEx function.
  2. Specify one or more check box styles in the dwStyle parameter of the CreateWindow or CreateWindowEx function.

    For a complete listing of supported styles, see, Appendix A, “Window and Control Styles.”

To create a check box in a dialog box

  • Add the following CHECKBOX resource-definition statement to your DIALOG resource.
    CHECKBOX text, id, x, y, width, height [[, style [[, extended-style]]]]

    Here, text is the text displayed to the right of the control and id is the value that identifies the check box. The control’s upper left corner is positioned at x, y, and its dimension is determined by width and height. Style and extended-style determine the appearance of the check box. The CHECKBOX resource statement creates a manual check box. This means that your application must manually check and uncheck the box each time the user selects the control. If you want Windows CE to toggle between checked and unchecked states when a user selects the control, use the AUTOCHECKBOX resource statement. For a complete listing of supported styles, see Window and Control Styles

A push button, also known as a command button, is a small, rectangular control that a user can turn on or off by tapping it with the stylus. A push button has a raised appearance in its default, or off, state and a depressed appearance when it is turned on. Windows CE supports owner-drawn push buttons discussed later in this chapter.

When the user taps a push button, it receives the keyboard focus from Windows CE, which sends the button’s parent window a WM_COMMAND message containing the BN_CLICKED notification code. In response, the dialog box closes and carries out the operation indicated by the button.

Note Windows CE does not support the BS_BITMAP, BS_FLAT, BS_ICON, BS_PUSHBOX, BS_TEXT, or BS_USERBUTTON styles. Use the BS_OWNERDRAW style to create the effects you would otherwise achieve by using the BS_BITMAP, BS_ICON, or BS_USERBUTTON button styles.

Push button

To create a push button using CreateWindow

  1. Specify the BUTTON window class in the lpClassName parameter of the CreateWindow or CreateWindowEx functions
  2. Specify one or more push box styles in the dwStyle parameter of the CreateWindow or CreateWindowEx functions

    For a complete listing of supported styles, see Window and Control Styles

To create a push button in a dialog box

  • Add the following PUSHBUTTON resource-definition statement to your DIALOG resource.
    PUSHBUTTON “string”, id, x, y, width, height [[, style [[, extended-style]]]]

    Here, string is the text you want displayed inside the push button, id is the value that identifies the push button. The control’s upper left corner is positioned at x, y, and its dimension is determined by width and height. Style and extended-style determine the push button appearance.

A radio button, also known as an option button, is similar to a check box in that you can select from one or more options. However, unlike a check box, when there are multiple radio buttons, only one item may be selected, making radio buttons mutually exclusive.

When the user selects an automatic radio button, Windows CE sets the check state of all other radio buttons within the same group to unchecked. For standard radio buttons, use the WS_GROUP style to achieve the same effect.

Windows CE supports most of the radio button styles that Windows-based desktop platforms support; it does not support the BS_LEFTTEXT style, that places the radio button to the right of the associated text. You can achieve the same effect by using the BS_RIGHTBUTTON style.

Radio button

To create a radio button using CreateWindow

  1. Specify the BUTTON window class in the lpClassName parameter of the CreateWindow or CreateWindowEx function.
  2. Specify one or more radio button styles in the dwStyle parameter of the CreateWindow or CreateWindowEx function.

    For a complete listing of supported styles, see Window and Control Styles

To create a radio button in a dialog box

  • Add the following RADIOBUTTON resource-definition statement to your DIALOG resource.
    RADIOBUTTON “string”, id, x, y, width, height [[, style [[, extended-style]]]]

    Here, string is the text you want displayed inside the radio button, id is the value that identifies the radio button. The control’s upper left corner is positioned at x, y, and its dimension is determined by width and height. Style and extended-style determine the appearance of the radio button. The RADIOBUTTON resource statement creates a manual radio button. This means that your application must manually clear other radio buttons in the group each time the user selects a button. If you want Windows CE to automatically clear other radio buttons when a user selects an option, use the AUTORADIOBUTTON resource statement.

A group box is a rectangular area within a dialog box in which you can group together controls semantically related. Controls are grouped by drawing a rectangular border around them. Any text associated with the group box is displayed in its upper-left corner. The sole purpose of a group box is to organize controls related by a common purpose, usually indicated by the label. The group box has only one style, defined by the constant BS_GROUPBOX. Because a group box cannot be selected, it has no check state, focus state, or push state. An application cannot send messages to a group box.

Because group boxes are opaque in Windows CE, add them to your dialog box template after you add other elements. Anything you add to the template after you add the group box will be hidden underneath it. By adding group boxes last, you ensure that the group boxes are at the bottom of the z-order and will not hide your other controls. The z-order is a stack of overlapping windows.

Group box

To create a group box using CreateWindow

  1. Specify the BUTTON window class in the lpClassName parameter of the CreateWindow or CreateWindowEx function.
  2. Specify one or more group box styles in the dwStyle parameter of the CreateWindow or CreateWindowEx function.

    For a complete listing of supported styles, see Window and Control Styles

To create a group box in a dialog box

  • Add the following GROUPBOX resource-definition statement to your DIALOG resource.
    GROUPBOX “title”, id, x, y, width, height [[, style [[, extended-style]]]]

    Here, title is the title of the box, id is the value that identifies the group box. The control’s upper left corner is positioned at x, y, and its dimension is determined by width and height. Style and extended-style determine the appearance of the group box.

When the user selects a button, either the operating system or the application must change one or more of the button’s state elements. A button’s state can be characterized by its focus state, push state, and check state. Windows CE automatically changes the focus state for all button types, the push state for push buttons, and the check state for all automatic buttons. The application must make all other state changes, taking into account the button’s type, style, and current state. An application can determine a button’s state by sending it a BM_GETCHECK or BM_GETSTATE message; the application can set a button’s state by sending it a BM_SETCHECK or BM_SETSTATE message.


Handling Button Messages

When the user selects a button, its state changes, and the button sends notification messages to its parent window about the changed state. For example, a push button control sends the BN_CLICKED notification message when the user selects the button. In all cases, the low-order word of the wParam parameter contains the control identifier, the high-order word of wParam contains the notification code, and the lParam contains the control window handle. Both the message and the parent window’s response to it depend on the type, style, and current button state.

For automatic buttons, the OS handles all state changes and the application processes only the BN_CLICKED notification message. For buttons that are not automatic, the application usually responds to the notification message by sending a message to change the state of the button. When the user selects an owner-drawn button, the button sends its parent window a WM_DRAWITEM message containing the identifier of the control to be drawn and information about its dimensions and state.

A button can also receive messages. A parent window can send messages to a button in an overlapped or child window by using the SendMessage function. It can send messages to a button in a dialog box by using the SendDlgItemMessage and CheckRadioButton functions.

Windows also provides default color values for buttons. The system sends a WM_CTLCOLORBTN message to a button’s parent window before the button is drawn. This message contains a handle of the button’s device context and a handle of the child window. The parent window can use these handles to change the button’s text and background colors. An application can retrieve the default values for these colors by calling the GetSysColor function, or it can set the values by calling the SetSysColors function.

The window procedure for the predefined button control window class processes defaults for all messages that the button control procedure does not process. When the button control procedure returns FALSE for any message, the predefined window procedure checks the messages.


Creating an Edit Control

An edit control, also called a text box, is a rectangular window in which a user can enter and edit text. Generally, you provide a label for an edit control by placing a static control with the appropriate text above or next to the edit control. However, if you do not have enough space to do this, you can include the label as the default text inside the edit control, enclosed between angle brackets, for example, <edit control label>.

To create an edit control using CreateWindow

  1. Specify the EDIT window class in the lpClassName parameter of the CreateWindow or CreateWindowEx function.
  2. Specify one or more edit control styles in the dwStyle parameter of the CreateWindow or CreateWindowEx function.

    The edit control style values you select can establish the appearance of a single-line or multiline edit control, align the text in the control, and determine if and how text appears in the edit control. The number and type of styles the application uses depend on the type and purpose of the edit control. For a complete listing of supported styles, see Window and Control Styles

The following code example shows how to use CreateWindow to create an edit control.

#define EDITID 1

// Specify the edit control window style.
DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
                WS_BORDER | ES_LEFT | ES_MULTILINE | ES_NOHIDESEL |
                ES_AUTOHSCROLL | ES_AUTOVSCROLL;

// Create the edit control window.
g_hwndEdit = CreateWindow (
                TEXT("edit"),   // Class name
                NULL,           // Window text
                dwStyle,        // Window style
                0,              // x coordinate of the upper left corner
                0,              // y coordinate of the upper left corner
                CW_USEDEFAULT,  // Width of the edit control window
                CW_USEDEFAULT,  // Height of the edit control window
                hwnd,           // Window handle of parent window
                (HMENU) EDITID, // Control identifier
                g_hInst,        // Instance handle
                NULL);          // Specify NULL for this parameter when
                                // creating a control

To create an edit control in a dialog box

  • Add the following EDITTEXT resource-definition statement to your DIALOG resource.
    EDITTEXT id, x, y, width, height [[, style [[, extended-style]]]]

    Here, id is the value that identifies the edit box. The control’s upper left corner is positioned at x, y, and its dimension is determined by width and height. Style and extended-style determine the appearance of the edit box. For a complete listing of supported styles, see Window and Control Styles

Edit control


Modifying the Text Buffer

When Windows CE creates an edit control, it automatically creates a text buffer, sets its initial size, and increases the size as necessary. Windows CE stores edit control text in a text buffer and copies it to the control. The size can be up to a predefined limit of 30,000 characters for single-line edit controls. Because this limit can change, it is a soft limit. You can set a hard limit to the buffer size by sending an EM_SETLIMITTEXT message to the edit control. If the buffer exceeds either limit, Windows CE sends the application an EN_ERRSPACE message. You can retrieve the current text limit by sending an EM_GETLIMITTEXT message.

You free the buffer by calling the LocalFree function, or you can obtain a new buffer, and buffer handle, by calling the LocalAlloc function.

You can initialize or reinitialize an edit control’s text buffer by calling the SetDlgItemText function. It can retrieve the content of a text buffer by calling the GetDlgItemText function.

For each edit control, Windows CE maintains a read-only flag that indicates whether the control’s text is read/write, which is the default, or read-only. An application can set the read/write or read-only flag for the text by sending the control an EM_SETREADONLY message. To determine whether an edit control is read-only, an application can call the GetWindowLong function using the GWL_STYLE constant. The EM_SETREADONLY message applies to both single-line and multiline edit controls.

You can change the font that an edit control uses by sending the WM_SETFONT message. Changing the font does not change the size of the edit control; applications that send the WM_SETFONT message may have to retrieve the font metrics for the text and recalculate the size of the edit control.


Changing the Formatting Rectangle

The visibility of an edit control’s text is governed by the dimensions of its window rectangle and its formatting rectangle. The window rectangle is the client area of the window containing the edit control. The formatting rectangle is a construct maintained by Windows CE for formatting the text displayed in the window rectangle. When an edit control is first displayed, the two rectangles are identical on the screen. An application can make the formatting rectangle larger or smaller than the window rectangle. Making the formatting rectangle larger limits the visibility of the edit control’s text; making it smaller creates extra white space around the text.

You can set the coordinates of an edit control’s formatting rectangle by sending it an EM_SETRECT message. The EM_SETRECT message automatically redraws the edit control’s text. To establish the coordinates of the formatting rectangle without redrawing the control’s text, send the control an EM_SETRECTNP message. To retrieve the coordinates of the formatting rectangle, send the control an EM_GETRECT message. These messages apply to multiline edit controls only.


Working with Text

After selecting an edit control, the user can select text in the control by using a pointing device or keyboard keys. You can retrieve the starting and ending character positions of the current selection in an edit control by sending the control an EM_GETSEL message.

You can also select text in an edit control by sending the control an EM_SETSEL message with the starting and ending character indexes for the selection. For example, you can use EM_SETSEL with EM_REPLACESEL to delete text from an edit control. These three messages apply to both single-line and multiline edit controls.

You can replace selected text in an edit control by sending the control an EM_REPLACESEL message with a pointer to the replacement text. If there is no current selection, EM_REPLACESEL inserts the replacement text at the insertion point. You might receive an EN_ERRSPACE notification message if the replacement text exceeds the available memory. This message applies to both single-line and multiline edit controls. You can use EM_REPLACESEL to replace part of an edit control’s text or the SetDlgItemText function to replace all of it.

Manipulating Text

Windows CE provides four messages for moving text between an edit control and the clipboard. These four messages apply to both single-line and multiline edit controls. The following table describes these messages.

Message
Description
WM_COPY | Copies the current selection, if any, from an edit control to the clipboard without deleting it from the edit control
WM_CUT | Deletes the current selection, if any, in the edit control and copies the deleted text to the clipboard
WM_CLEAR | Deletes the current selection, if any, from an edit control, but does not copy it to the clipboard unless the user presses the SHIFT key
WM_PASTE | Copies text from the clipboard into an edit control at the insertion point

When the user selects, deletes, or moves text in an edit control, Windows CE maintains an internal flag for each edit control indicating whether the content of the control has been modified. Windows CE clears this flag when it creates the control and sets the flag when the text in the control is modified. You can retrieve the modification flag by sending the control an EM_GETMODIFY message and set or clear the modification flag by sending the control an EM_SETMODIFY message. These messages apply to both single-line and multiline edit controls.

The default limit of text a user can type in an edit control is 30,000 characters. You can change the amount of text the user can type by sending the control an EM_SETLIMITTEXT message. This message sets a hard limit to the number of bytes the user can type into an edit control, but affects neither text already in the control when the message is sent nor text copied to the control by the SetDlgItemText function or the WM_SETTEXT message. For example, suppose you use the SetDlgItemText function to place 500 characters in an edit control, and the user also types 500 characters in the edit control, creating a total of 1,000 characters. If you send an EM_SETLIMITTEXT message limiting user-entered text to 300 characters, the 1,000 characters already in the edit control remain there, and the user cannot add any more text. On the other hand, if you send an EM_SETLIMITTEXT message limiting user-entered text to 1,300 characters, the 1,000 characters remain, but the user can add 300 more characters.

When the user reaches the character limit of an edit control, Windows CE sends the application a WM_COMMAND message containing an EN_MAXTEXT notification message. This notification message does not mean that memory has been exhausted, but that the limit for user-entered text has been reached; the user cannot type any more text. To change this limit, you must send the control a new EM_SETLIMITTEXT message with a higher limit.

Working with Wordwrap Functions

You may direct a multiline edit control to add or remove a soft line break character—two carriage returns and a linefeed—automatically at the end of wrapped text lines. An application can turn this feature on or off by sending the edit control an EM_FMTLINES message. This message applies only to multiline edit controls and does not affect a line that ends with a hard line break—one carriage return and a linefeed typed by the user.

Retrieving Points and Characters

To determine which character is closest to the specified point in an edit control by sending the EM_CHARFROMPOS message. The message returns the character index and line index of the character nearest the point. Similarly, you can determine the client coordinates of the specified character in an edit control by sending the EM_POSFROMCHAR message. You specify the index of a character and the message returns the x- and y-coordinates of the upper-left corner of the character.

Undoing Text Operations

Every edit control maintains an undo flag that indicates whether an application can reverse, or undo, the most recent operation on the edit control; for example, to undo a text deletion. The edit control sets the undo flag to indicate that the operation can be undone and resets it to indicate that the operation cannot be undone. You can determine the setting of the undo flag by sending the control an EM_CANUNDO message.

To undo the most recent operation by sending the control an EM_UNDO message. An operation can be undone provided no other edit control operation occurs first. For example, the user can delete text; replace the text or undo the deletion; and then delete the text again or undo the replacement. The EM_UNDO message applies to both single-line and multiline edit controls and always works for single-line edit controls.


Scrolling Text in an Edit Control

To implement scrolling in an edit control, you can use the automatic scrolling styles, or you can explicitly add scroll bars to the edit control. To add a horizontal scroll bar, use the style WS_HSCROLL; to add a vertical scroll bar, use the style WS_VSCROLL. An edit control with scroll bars processes its own scroll bar messages.

Windows CE provides three messages that you can send to an edit control with scroll bars. The EM_LINESCROLL message can scroll a multiline edit control both vertically and horizontally. The lParam parameter specifies the number of lines to scroll vertically starting from the current line and the wParam parameter specifies the number of characters to scroll horizontally, starting from the current character. The edit control does not acknowledge messages to scroll horizontally if it has the ES_CENTER or ES_RIGHT style. This message applies to multiline edit controls only.

The EM_SCROLL message scrolls a multiline edit control vertically, which is the same effect as sending a WM_VSCROLL message. The wParam parameter specifies the scrolling action. The EM_SCROLL message applies to multiline edit controls only.


Adding Tab Stops and Margins

To set tab stops in a multiline edit control, use the EM_SETTABSTOPS message. The default for a tab stop is eight characters. When you add text to the edit control, tab characters in the text automatically generate space up to the next tab stop. The EM_SETTABSTOPS message does not automatically cause Windows CE to redraw the text. To do that, you can call the InvalidateRect function. The EM_SETTABSTOPS message applies to multiline edit controls only.

To set the width of the left and right margins for an edit control, use the EM_SETMARGINS message. After sending this message, Windows CE redraws the edit control to reflect the new margin settings. You can retrieve the width of the left or right margin by sending the EM_GETMARGINS message. By default, the edit control margins are set to be just wide enough to accommodate the largest character horizontal overhang, known as a negative ABC width, for the font currently in use in the edit control.


Using Password Characters

You can use a password character in an edit control to conceal user input. When a password character is set, it is displayed in place of each character typed. When a password character is removed, the control displays the characters the user types. If you create an edit control using the style ES_PASSWORD, the default password character is an asterisk. An application can use the EM_SETPASSWORDCHAR message to remove or define a different password character and the EM_GETPASSWORDCHAR message to retrieve the current password character. These messages apply to single-line edit controls only.


Creating a List Box

A list box is a window that displays a list of character strings. The user selects a string from the list by tapping it with the stylus. When a string is selected, it is highlighted. List boxes are typically placed in dialog boxes, but they can also be placed in a normal window by specifying the LISTBOX window class in the lpClassName parameter of the CreateWindow or CreateWindowEx function.

To create a listbox control in a dialog box

  • Add the following LISTBOX resource-definition statement to the DIALOG resource.
    LISTBOX id, x, y, width, height [[, style [[, extended-style]]]]

    Here, id is the value that identifies the listbox. The control’s upper left corner is positioned at x, y, and its dimension is determined by width and height. Style and extended-style determine the appearance of the listbox. The Win32 API provides two types of list boxes: single-selection, which is the default, and multiple-selection. In a single-selection list box, the user can select only one item at a time. In a multiple-selection list box, the user can select more than one item at a time. To create a multiple-selection list box, specify the LBS_MULTIPLESEL or the LBS_EXTENDEDSEL style.

    Note Windows CE supports the LBS_EX_CONSTSTRINGDATA style which saves RAM when a large table of strings in ROM are inserted into a list box.

    All list boxes in Windows CE have the LBS_HASSTRINGS style by default. Windows CE does not support owner-drawn list boxes. For a complete listing of supported styles, see Window and Control Styles

Because list boxes are empty by default, you must initialize or populate the list box with data when the dialog box containing the list box is displayed. Each time a dialog box is activated, the system sends your dialog box procedure a WM_INITDIALOG message. A dialog box procedure is responsible for initializing and monitoring its child windows, including any list boxes. The dialog box procedure communicates with the list box by sending messages to it and by processing the notification messages sent by the list box. To initialize the list box, modify your dialog box procedure by adding a case to the SWITCH statement in you dialog box procedure. The following code example shows how to do this.

#define IDC_FONTLIST  1000        // List control identifier

int iNumOfFonts = 0;              // The total number of fonts
int iCurrItem = -1;               // The index of newly selected or
                                  // added font name in the list box
BOOL CALLBACK FontListDlgProc (HWND hwndDlg, UINT uMsg, WPARAM wParam,
                               LPARAM lParam)
{
  int index = 1,
      iFontIndex;           // Font list index
  TCHAR szFontName[80],     // For the currently enumerated font name
        szFontNamePrev[80]; // For the previously enumerated font name
  HWND hwndFontListCtrl;    // Window handle of the font listbox

  // Get the window handle of the font listbox control in the dialog box
  hwndFontListCtrl = GetDlgItem (hwndDlg, IDC_FONTLIST);

  switch (uMsg)
  {
    case WM_INITDIALOG:
      // Need to find the total number of fonts, iNumOfFonts, prior to
      // the following code.

      for (iFontIndex = 0; iFontIndex < iNumOfFonts; ++iFontIndex)
      {
        // Insert code here to retrieve the name of each font and then
        // put it in szFontName.
        // ...

        // Add the font name to the list control.
        iCurrItem = SendMessage (hwndFontListCtrl, LB_ADDSTRING, 0,
                                 (LPARAM)(LPCTSTR) szFontName);

        // Set a 32-bit value, (LPARAM) iFontIndex,  associated with the
        // newly added item in the list control.
        SendMessage (hwndFontListCtrl, LB_SETITEMDATA,
                     (WPARAM) iCurrItem, (LPARAM) iFontIndex);
      }

      // Select the first font name in the list control.
      SendMessage (hwndFontListCtrl, LB_SETCURSEL, 0, 0);

      return TRUE;

    case WM_COMMAND:
      switch (LOWORD (wParam))
      {
        case IDOK:
          // Retrieve the index of the currently selected font.
          if ((iCurrItem = SendMessage (hwndFontListCtrl, LB_GETCURSEL,
                                        0, 0)) != LB_ERR)
          {
            iFontIndex = SendMessage (hwndFontListCtrl, LB_GETITEMDATA,
                                      iCurrItem, 0);

            // Get the currently selected font from the index.
            CurrLogFont = g_lpEnumLogFont[iFontIndex].elfLogFont;

            EndDialog (hwndDlg, 0);
            return TRUE;
          }

        case IDCANCEL:
          iCurrItem = -1;
          EndDialog (hwndDlg, 0);
          return TRUE;
      }
      break;
  }
  return FALSE;
}

List box


Creating a Combo Box

A combo box is a control that combines a list box with an edit control. Selecting an item in the list box displays the selected text in the edit control. If the combo box style accepts keyboard input, typing characters into the edit control highlights the first list box item that matches the characters typed. A combo box can appear either in a dialog box or on the command bar.

To create a combo control in a dialog box

  • Add the following COMBOBOX resource-definition statement to your DIALOG resource.
    COMBOBOX id, x, y, width, height [[, style [[, extended-style]]]]

    Here, id is the value that identifies the combo box. The control’s upper left corner is positioned at x, y, and its dimension is determined by width and height. Style and extended-style determine the appearance of the combo box. Because of limited screen space, Windows CE-based devices use either the CBS_DROPDOWN or CBS_DROPDOWNLIST style rather than the CBS_SIMPLE style popular on Windows-based desktop platforms. In the CBS_SIMPLE style, the list box is always visible and the current selection is displayed in the edit control. In the CBS_DROPDOWN or CBS_DROPDOWNLIST styles, the list box is not displayed until the user selects an icon next to the edit control, which conserves screen space. The difference between the two styles is that the CBS_DROPDOWNLIST style has a static text field that always displays the current selection instead of having an edit control.

    Note If you specify the CBS_EX_CONSTSTRINGDATA style when the application inserts a string into the list part of a combo box, the combo box stores the pointer passed to it by the application rather than copying the string. This saves RAM when you have a large table of strings in ROM that you want to insert into a combo box.

    All list boxes in Windows CE have the LBS_HASSTRINGS style by default. Windows CE does not support owner-drawn combo boxes. For a complete listing of supported styles, see Window and Control Styles

To create a command bar combo box and insert it into a command bar, use the CommandBar_InsertComboBox function.

Combo box


Working with Edit Control Selection Fields

The edit control selection field is the portion of a combo box that displays the currently selected list item. In drop-down combo boxes, which are combo boxes that have the CBS_DROPDOWN style, the selection field is an edit control and can be used to type text not in the list.

You can retrieve or set the contents of the edit control selection field and can determine or set the edit selection. You can also limit the amount of text a user can type in the selection field. When the contents of the selection field change, Windows CE sends notification messages to the parent window or dialog box procedure.

To retrieve the content of the edit control selection field, you send a WM_GETTEXT message to the combo box. To set the contents of the selection field of a drop-down combo box, you send the WM_SETTEXT message to the combo box.


Creating a Scroll Bar

A scroll bar is used to scroll text in a window. Scroll bars should be included in any window for which the content of the client area extends beyond the window borders. A scroll bar’s orientation determines the direction in which scrolling occurs when the user operates the scroll bar. A horizontal scroll bar enables the user to scroll the content of a window to the left or right. A vertical scroll bar enables the user to scroll the content up or down.

You can use as many scroll bar controls as needed in a single window. When you create a scroll bar control, you must specify the scroll bar’s size and position. However, if a scroll bar control’s window can be resized, your application must adjust the scroll bar’s size when the size of the window changes.

To create an scroll bar using CreateWindow

  1. Specify the SCROLLBAR window class in the lpClassName parameter of the CreateWindow or CreateWindowEx function.
  2. Specify one or more scroll bar control styles in the dwStyle parameter of the CreateWindow or CreateWindowEx functions.

    A scroll bar control can have a number of styles to control the orientation and position of the scroll bar. Some of the styles create a scroll bar control that uses a default width or height. However, you must always specify the x and y coordinates and the other scroll bar dimensions.For a complete listing of supported styles, see Window and Control Styles

The following code example shows how to use CreateWindow to create a scrollbar.

#define SCROLLBARID 100

DWORD dwStyle = SBS_BOTTOMALIGN | SBS_HORZ | WS_VISIBLE | WS_CHILD;

hwndSB = CreateWindow (
            TEXT("scrollbar"),  // Class name
            NULL,               // Window text
            dwStyle,            // Window style
            0,                  // x coordinate of the upper left corner
            0,                  // y coordinate of the upper left corner
            CW_USEDEFAULT,      // The width of the edit control window
            CW_USEDEFAULT,      // The height of the edit control window
            hwnd,               // Window handle of parent window
            (HMENU) SCROLLBARID,// The control identifier
            hInst,              // The instance handle
            NULL);              // Specify NULL for this parameter when
                                // creating a control

To create an scroll bar control in a dialog box

  • Add the following SCROLLBAR resource-definition statement to your DIALOG resource.
    SCROLLBAR id, x, y, width, height [[, style [[, extended-style]]]]

    Here, id is the value that identifies the scroll bar.

    The x and y parameters determine the scroll bar position and is represented as an integer. It is relative to the left or upper end of the scroll bar, depending on whether the scroll bar is horizontal or vertical. The position must be within the minimum and maximum values of the scrolling range. For example, in a scroll bar with a range from 0 through 100, position 50 is the middle, with the remaining positions distributed equally along the scroll bar. The initial range depends on the scroll bar. Standard scroll bars have an initial range from 0 through 100. Scroll bar controls have an empty range—both minimum and maximum values are zero—unless you supply an explicit range when you create the control. You can alter the range at any time after its initial creation. You can use the SetScrollInfo function to set the range values, and the GetScrollInfo function to retrieve the current range values.

    The width and height parameters determine the scrollbar size. You can set a scroll bar equal to a page size. The page size represents the number of data units that can fit in the client area of the owner window given its current size. For example, if the client area can hold eight lines of text, an application would set the page size to eight. Windows CE uses the page size, along with the scrolling range and length of the scroll bar’s gray area, to set the size of the scroll bar. When a window containing a scroll bar is resized, an application should call the SetScrollInfo function to set the page size. An application can retrieve the current page size by calling the GetScrollInfo function.

    Style and extended-style determine the appearance of the edit box. The default style of a scroll bar is SBS_HORZ, which creates a horizontal scroll bar. For a vertical scroll bar, specify the SBS_VERT style.For a complete listing of supported styles, see Window and Control Styles

Scroll bar

To establish a useful relationship between the scroll bar range and the data object, your application must adjust the range when the size of the data object changes.

As the user moves the scroll box in a scroll bar, the scroll bar reports the scroll box position as an integer in the scrolling range. If the position is the minimum value, the scroll box is at the top of a vertical scroll bar or at the left of a horizontal scroll bar. If the position is the maximum value, the scroll box is at the bottom of a vertical scroll bar or at the right end of a horizontal scroll bar.

Your application must move the scroll box in a scroll bar. Although the user makes a request for scrolling in a scroll bar, the scroll bar does not automatically update the scroll box position. Instead, it passes the request to the parent window, which must scroll the data and update the scroll box position. Use the SetScrollInfo function in your application to update the scroll box position. Because your application controls the scroll box movement relative to the window data object, you determine the incremental position settings for the scroll box that work best for the data being scrolled.


Handling Scroll Bar Requests

Unlike other controls, a scroll bar does not send WM_COMMAND messages. Instead, the scroll bar sends WM_HSCROLL and WM_VSCROLL messages to the window procedure when the user taps the scroll bar or drags the scroll box. The low-order words of WM_VSCROLL and WM_HSCROLL each contain a notification message that indicates the direction and magnitude of the scrolling action.

When you process the WM_HSCROLL and WM_VSCROLL messages, you should examine the scroll bar notification message and calculate the scrolling increment. After you apply the increment to the current scrolling position, you can scroll the window to the new position by using the ScrollWindowEx function. You can use the SetScrollInfo function to adjust the position of the scroll box.

The corresponding SBM_SETSCROLLINFO message passes parameters into the SCROLLINFO structure to indicate the new scroll box position. The SBM_GETSCROLLINFO message retrieves the current position contained within the SCROLLINFO structure.

After you scroll a window, it makes part of the window’s client area invalid. To ensure that the invalid region is updated, you use the UpdateWindow function to generate a WM_PAINT message.

Usually an application scrolls the content of a window in the direction opposite that indicated by the scroll bar. For example, when the user taps the gray area below the scroll box, an application scrolls the object in the window upward to reveal a portion of the object below the visible portion. An application can also scroll a rectangular region using the ScrollDC function.

When you process the WM_CREATE message you can set scrolling units. It is convenient to base the scrolling units on the dimensions of the font associated with the window display context (DC). To retrieve the font dimensions for a specific DC, use the GetTextMetrics function. When you process the WM_SIZE message, you can adjust the scrolling range and scrolling position to reflect the dimensions of the client area as well as the number of lines of text displayed.


Creating a Static Control

A static control is a control used to display text, to draw frames or lines separating other controls, or to display icons. A static control does not accept user input, but it can notify its parent window of a stylus tap if the static control is created with SS_NOTIFY style.

Static control

Although you can use static controls in overlapped, pop-up, and child windows, they are designed for use in dialog boxes, where Windows CE standardizes their behavior. If you use static controls outside of dialog boxes, you increase the risk that the application might behave in a nonstandard fashion. Windows CE does not support owner-drawn static controls.


Choosing a Control Style

In Windows CE, you can use only the SS_CENTERIMAGE style in conjunction with the SS_BITMAP style. Even if you specify SS_ICON, you cannot set the image by calling:

SendMessage( hStatic, STM_SETIMAGE, IMAGE_ICON, (LPARAM) hIcon );

You have to use:

SendMessage( hStatic, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM) hBitmap );

If you specify SS_CENTERIMAGE, and do not specify either SS_ICON or SS_BITMAP, the static control will behave as though you had specified the SS_BITMAP style.

Windows CE does not support the SS_SIMPLE static control styles, but you can emulate this style by using the SS_LEFT or SS_LEFTNOWORDWRAP style. Windows CE also does not support the SS_BLACKFRAME, SS_BLACKRECT, SS_GRAYFRAME, SS_GRAYRECT, SS_OWNERDRAW, SS_WHITEFRAME, SS_WHITERECT styles, but you can use the WM_PAINT message to achieve the same results.


Creating an Application-Defined Window Control

You can create custom, application-defined window controls to perform tasks not supported by predefined controls. Windows CE provides the following ways to create a custom control:

  • Use owner-drawn buttons
  • Use the subclass procedure to produce a custom control
  • Register and implement an application-defined window class

Buttons have owner-drawn styles available that direct the control to send a message to the parent window when the control must be drawn. This feature enables you to alter the appearance of a control. For buttons, the owner-drawn style affects how the system draws the entire control.

You can designate buttons as owner-drawn controls by creating them with the appropriate style. When a control has the owner-drawn style, Windows CE handles the user’s interaction with the control as usual, performing such tasks as detecting when a user has chosen a button and then notifying the button’s owner of the event. However, because the control is owner-drawn, the parent window of the control is responsible for the visual appearance of the control.

When you use the BS_OWNERDRAW style for a button, you assume all responsibility for drawing the button. You cannot use any other button styles with the BS_OWNERDRAW style. When you use an owner-drawn button, you have to trap the WM_DRAWITEM message in the window procedure for the button’s parent window and insert the code that erases the background, if necessary, and draws the button.

The WM_DRAWITEM message is not generated by the window manager; it is part of the interface between a button and its owner. When you use a built-in button class, the button’s window procedure automatically sends the WM_DRAWITEM message to the button’s parent window when the button receives a WM_PAINT message. If you create a new class of button—a button that is not a built-in button—and you want it to support the WM_DRAWITEM message, you must send the WM_DRAWITEM message to the button’s parent window when the button needs to be redrawn.

You can use the subclass procedure to create a custom control. The subclass procedure alters selected behaviors of the control by processing those messages that affect the selected behaviors. All other messages pass to the original window procedure for the control.

You can create custom controls by registering an application-defined window class and specifying the name of the window class in the CreateWindowEx function or in the dialog box template. The process for registering an application-defined window class for a custom control is the same as for registering a class for an ordinary window. Each class must have a unique name, a corresponding window procedure, and other information.

At a minimum, the window procedure draws the control. If an application uses the control to let the user type information, the window procedure also processes input messages from the keyboard and stylus and sends notification messages to the parent window. In addition, if the control supports control messages, the window procedure processes messages sent to it by the parent window or other windows. For example, controls often process the WM_GETDLGCODE message sent by dialog boxes to direct a dialog box to process keyboard input in a specified way.

Because an application-defined control message is specific to the designated control, you must explicitly send it to the control by using the SendMessage or SendDlgItemMessage function. The numeric value for each message must be unique and must not conflict with the values of other window messages.


Working with Common Controls

Common controls are a set of windows supported by the common control library, which is a DLL included with the Windows CE OS. Like other control windows, a common control is a child window that an application uses in conjunction with another window to perform I/O tasks.

Common controls offer users a familiar interface for performing common tasks, which makes applications easier to use and learn. Most common controls send the WM_NOTIFY message instead of the WM_COMMAND message sent by window controls.

The following list shows common controls supported by Windows CE.

Command bars | Tree views
Command bands | Up-down controls
Rebars | Date and time picker
Toolbars | Month calendar controls
ToolTips | Status bars
Header controls | Progress bars
Image lists | Property sheets
List views | Tab controls
Trackbars |

Windows CE does not support the following controls commonly used on Windows-based desktop platforms: animation controls, ComboBoxEx controls, drag lists, flat scroll bars, hot keys, Internet Protocol (IP) address controls, or rich ink edit controls.

Before you can create or use any common controls, you must register them. You can do this in either of two ways: call the InitCommonControls function, which registers all the common controls at once, except for the rebar, month calendar, and date and time picker controls, or call the InitCommonControlsEx function, which registers a specific common control class. Calling either of these functions ensures that the common DLL is loaded.

To use most of the common controls, you must include the Commctrl.h header file in your application. To use property sheets, you must include the Prsht.h header file.

When creating a common control, it is important to understand that all common controls are child windows that you create by calling CreateWindowEx. You can also create a common control by calling a control-specific API function. Because common controls are windows, you can manage them the same way as you manage other application windows.

Though Windows CE supports some styles that apply to a broad spectrum of common controls, each of the common controls also has a set of styles unique to that control. Unless noted otherwise, these unique styles apply to header controls, toolbar controls, rebars, and status windows. For a complete listing of supported styles, see Window and Control Styles


Creating a Command Bar

A command bar is a toolbar that can include a menu bar as well as the Close (X) button, the Help (?) button, and the OK button, usually found on the title bar of Windows-based desktop applications. A command bar can contain menus, combo boxes, buttons, and separators. A separator is a blank space you can use to divide other elements into groups or to reserve space in a command bar. You create a command bar to organize your application menus and buttons.

You create a command bar by using the CommandBarCreate function. Windows CE registers this class when it loads the common control DLL. You can use the InitCommonControls function to ensure that this DLL is loaded.

Windows CE command bar

To create a command bar

  1. Create the commands bands control by calling the CommandBar_Create function.
  2. Add controls to the command bar by calling the CommandBar_InsertMenubar, CommandBar_AddBitmap, CommandBar_AddButtons, and CommandBar_InsertComboBox functions.
  3. Add the Close and Help buttons by calling the CommandBar_AddAdornments function and passing CMDBAR_HELP in the dwFlags parameter. Windows CE automatically adds the Close button.

In addition to creating and registering command bars, Windows CE supports many functions you can use to manipulate a command bar.

The following table shows how to manipulate a command bar control.

To
Call
Add the Close (X), Help (?), and the OK button to the command bar. Minimally, every command bar must have a Close button. | CommandBar_AddAdornments
Destroy the command bar without destroying the parent window. | CommandBar_Destroy
Add a single button or separator to a command bar. | CommandBar_InsertButton
Add several buttons or separators at once to a command bar. When creating a separator, specify TBSTYLE_SEP as the fsStyle member of the TBBUTTON structure you pass in the lpButton parameter. | CommandBar_AddButtons
Determine the usable portion of the application window. | GetClientRect
Subtract the height of the command bar from the size of the client rectangle. | CommandBar_Height
Determine whether a command bands control is visible. | CommandBands_GetRestoreInformation
Add ToolTips describing the command bar buttons. | CommandBar_AddTooltips
Show or hide the command bands control. | CommandBands_Show
Determine if a command bar is visible. | CommandBar_IsVisible
Create a combo box and insert it into a command bar. This function always creates a combo box with the WS_CHILD and WS_VISIBLE styles. | CommandBar_InsertComboBox
Insert a menubar, identified by a resource identifier, into a command bar. | CommandBar_InsertMenubar
Insert a menubar, identified by a resource name or menu handle, into a command bar. | CommandBar_InsertMenubarEx
Obtain the handle of a menu bar in a command bar. | CommandBar_GetMenu
Obtain the handle of a submenu on the menu bar. | GetSubMenu
Redraw the command bar after modifying a menu bar on the command bar. | CommandBar_DrawMenuBar

Each element in a command bar has a zero-based index by which command bar functions can identify it. The leftmost element has an index of zero, the element immediately to its right has an index of one, and so on. When you use any of the CommandBar_Insert functions, the menu bar, button, or combo box is inserted to the left of the button whose index you specify in the iButton parameter.

A command bar stores the information needed to draw the button images in an internal list, which is empty when the command bar is created. Each image has a zero-based index that you use to associate the image with a button. Use the CommandBar_AddBitmap function to add an array of images to the end of the list. This function returns the index of the first new image added. The system includes a set of predefined command bar buttons with header files that define constant values for their indexes.

Note Do not use 0xFFFFFFFF as the command identifier of a command bar control. This identifier is reserved for use by the command bar.

The following code example shows how to create a command bar.

// Create a command bar.
hwndCB = CommandBar_Create (hInst, hwnd, 1);

// Adds ToolTip strings to the command bar.
CommandBar_AddToolTips (hwndCB, uNumSmallTips, szSmallTips);

// Adds 15 images to the list of button images available for use
// in the command bar.
CommandBar_AddBitmap (hwndCB, HINST_COMMCTRL, IDB_STD_SMALL_COLOR,
                      15, 16, 16);

// Inserts the menu bar into the command bar.
CommandBar_InsertMenubar (hwndCB, hInst, IDM_MAIN_MENU, 0);

// Add buttons in tbSTDButton to the Command bar.
CommandBar_AddButtons (hwndCB,
                       sizeof (tbSTDButton) / sizeof (TBBUTTON),
                       tbSTDButton);

// Add help, OK and exit buttons to the command bar.
CommandBar_AddAdornments (hwndCB, WM_HELP | CMDBAR_OK, 0)

CommandBars do not automatically resize when you resize a main window. To resize a CommandBar along with a main window, wait until the main window receivies a WM_SIZE message. Then send a TB_AUTOSIZE message to the CommandBar and call CommandBar_AlignAdornments. If you do not perform this procedure, the OK, CANCEL, and HELP CommandBar buttons will not stay flush with the right border of the window when the window size changes. The following code example shows how to resize a CommandBar along with a main window.

case WM_SIZE:
   // Tell the CommandBar to resize itself to fill the top of the
      window.
   SendMessage(hwndCB, TB_AUTOSIZE, 0L, 0L);
   CommandBar_AlignAdornments(hwndCB);
   break;


Creating a Command Bands Control

The command bands control is a special kind of rebar control. It has a fixed band at the top containing a toolbar with a Close (X) button, and optionally, a Help (?) button and an OK button, in the right corner. By default, each band in the command bands control contains a command bar. You can override this, however, if you want a band to contain some other type of child window.

Windows CE command band

To create a command band control

  1. Initialize an INITCOMMONCONTROLSEX structure with (ICC_BAR_CLASSES | ICC_COOL_CLASSES) as the dwICC member.
  2. Register the command bands control class and the command bar class by calling the InitCommonControlsEx function, and passing in the INITCOMMONCONTROLSEX structure.
  3. Create the image list to use for the band images.
  4. Create the commands bands control by calling the CommandBands_Create function, and then passing the image list handle in the himl parameter.
  5. Initialize an array of REBARBANDINFO structures, one for each band in the command bands control.
  6. Add the bands by calling the CommandBands_AddBands function, passing the array of REBARBANDINFO structures in the prbbi parameter.
  7. Add controls to the command bars in the bands by calling the appropriate command bar functions for the controls you want to add.
  8. Call the CommandBands_AddAdornments function to add the Close and Help buttons. When you do this, the Close button is added by default.

The following code example demonstrates how to register and create a command band.

HWND WINAPI CreateCmdband (HWND hwnd)
{
  HWND hwndCBar = NULL,     // The handle of the command bar control
       hwndCBand = NULL;    // The handle of the command band control
  REBARBANDINFO rbi[3];     // REBARBANDINFO structures for commandband
  HIMAGELIST hImageList = NULL;
                            // Handle of the image list for commandband
  INITCOMMONCONTROLSEX iccex;
                            // INITCOMMONCONTROLSEX structure

  iccex.dwSize = sizeof (INITCOMMONCONTROLSEX);
  iccex.dwICC = ICC_BAR_CLASSES | ICC_COOL_CLASSES;

  // Registers toolbar and rebar control classes from the common control
  // dynamic-link library (DLL).
  InitCommonControlsEx (&iccex);

  // Create the image list for the command band.
  if (!(hImageList = ImageList_LoadImage (
                  hInst,
                  MAKEINTRESOURCE (IDB_BANDIMAGE),
                  16,
                  3,
                  CLR_DEFAULT,
                  IMAGE_BITMAP,
                  LR_DEFAULTCOLOR)))
    return NULL;

  // Create the command band control.
  if (!(hwndCBand = CommandBands_Create (
                  hInst,
                  hwnd,
                  ID_BAND,
                  RBS_VARHEIGHT | RBS_BANDBORDERS | RBS_AUTOSIZE,
                  hImageList)))
    return NULL;

  // REBARBANDINFO for the menu band.
  rbi[0].cbSize = sizeof (REBARBANDINFO);
  rbi[0].fMask = RBBIM_STYLE | RBBIM_ID | RBBIM_SIZE;
  rbi[0].fStyle = RBBS_CHILDEDGE | RBBS_NOGRIPPER;
  rbi[0].wID = ID_BAND_MENUBAR;
  rbi[0].cx = 150;

  // REBARBANDINFO for the main toolbar band.
  rbi[1].cbSize = sizeof (REBARBANDINFO);
  rbi[1].fMask = RBBIM_TEXT | RBBIM_ID | RBBIM_IMAGE | RBBIM_STYLE;
  rbi[1].fStyle = RBBS_BREAK | RBBS_GRIPPERALWAYS;
  rbi[1].lpText = TEXT("Toolbar");
  rbi[1].wID = ID_BAND_TOOLBAR;
  rbi[1].iImage = 0;

  // REBARBANDINFO for the font toolbar band.
  rbi[2].cbSize = sizeof (REBARBANDINFO);
  rbi[2].fMask = RBBIM_TEXT | RBBIM_ID | RBBIM_IMAGE | RBBIM_STYLE;
  rbi[2].fStyle = RBBS_GRIPPERALWAYS;
  rbi[2].lpText = TEXT("Font");
  rbi[2].wID = ID_BAND_FONT_TOOLBAR;
  rbi[2].iImage = 1;

  // Adds bands to the command bands control.
  if (!CommandBands_AddBands (hwndCBand, hInst, 3, rbi))
    return NULL;

  // Inserts a menu bar into the menu command band.
  if (hwndCBar = CommandBands_GetCommandBar (hwndCBand, 0))
    CommandBar_InsertMenubar (hwndCBar, hInst, IDM_MAIN_MENU, 0);

  // Add the buttons to the main toolbar band.
  if (hwndCBar = CommandBands_GetCommandBar (hwndCBand, 1))
  {
    CommandBar_AddBitmap (hwndCBar, hInst, IDB_TOOLBAR, 11, 0, 0);
    CommandBar_AddButtons (
                  hwndCBar,
                  sizeof (tbButtons) / sizeof (TBBUTTON),
                  tbButtons);
  }

  // Add the buttons to the font toolbar band.
  if (hwndCBar = CommandBands_GetCommandBar (hwndCBand, 2))
  {
    CommandBar_AddBitmap (hwndCBar, hInst, IDB_TOOLBAR, 11, 0, 0);
    CommandBar_AddButtons (
                  hwndCBar,
                  sizeof (tbFontButtons) / sizeof (TBBUTTON),
                  tbFontButtons);
  }

  // Add the help and close button to the command band.
  CommandBands_AddAdornments (hwndCBand, hInst, CMDBAR_HELP, NULL);

  return hwndCBand;
}

Once you create a command bands control, you may want to add additional controls to the band or resize the band. Windows CE supports several functions for manipulating command bands.

The following table shows how to manipulate a command bands control.

To
Call
Add a band with the Close (X) button, the Help (?) button, and the OK button. | CommandBands_AddAdornments
Add one or more bands to the control. By default, each band has a command bar as its child window. | CommandBands_AddBands
Create a command bands control. | CommandBands_Create
Retrieve a command bar from a band in a command bands control. Pass the zero-based index of the band that contains the command bar you want to retrieve. | CommandBands_GetCommandBar
Return the height of the CommandBands control. | CommandBands_Height
Gets the parent rectangle of the control. | GetClientRect
Determine whether a command bands control is visible. | CommandBands_IsVisible
Retrieve information about the bands in a command bands control, so you can save the information in the registry to restore the command bands control to a previous state. | CommandBands_GetRestoreInformation
Show or hide the command bands control. | CommandBands_Show

Because a command band is a rebar control and a toolbar control, you can also manipulate it using rebar and toolbar messages.

Command bands controls support the custom draw service, which makes it easy to customize the appearance of a command bands control. Creating a Rebar Control

A rebar control, which has one or more bands, is a container for child windows. Each band can contain one child window, which can be a toolbar or any other control. Each band can have its own bitmap, which is displayed as a background for the toolbar on that band. A user can resize or reposition a band by dragging its gripper bar. A gripper bar appears on a rebar or a command band control and is especially useful for bringing off-screen rebar or command bar controls into view. If a band has a text label next to its gripper bar, a user can maximize the band and restore it to its previous size by tapping the label with the stylus.

Windows CE rebar

Like other common controls, a rebar control sends WM_NOTIFY messages to its parent window. A rebar control also forwards to its parent window all messages it receives from the child windows assigned to its bands.

Rebar controls also support the Windows CE custom draw service, which makes it easy to customize a rebar control’s appearance. For more information on the custom draw service, see “Using Custom Draw Services” later in this chapter.

To create a rebar control

  1. Specify REBARCLASSNAME in the lpClassName parameter of the CreateWindowEx function.

    This class is registered when the common control DLL is loaded. You can also use the InitCommonControlsEx function to ensure that this DLL is loaded. To register the rebar control class using the InitCommonControlsEx function, specify the ICC_COOL_CLASSES flag as the dwICC member of the INITCOMMONCONTROLSEX structure you pass in the lpInitCtrls parameter.

  2. Specify a rebar style in the dwStyle parameter of the CreateWindowEx function.

    To place a toolbar inside a rebar, you must specify the CCS_NOPARENTALIGN style to ensure proper alignment.

    For a complete listing of supported styles, see Window and Control Styles

The following code example shows how to create a rebar control.

HWND CreateRebar (HWND hwnd)
{
  HWND hwndRB = NULL,         // The handle to the rebar control
       hwndTB = NULL,         // The handle to the toolbar
       hwndCombo = NULL;      // The handle to the combobox control
  DWORD dwStyle;              // The window style used in CreateWindowEx
  int index;                  // An integer
  RECT rect;                  // A RECT structure
  TCHAR szString[64];         // A temperary string
  HICON hIcon;                // A handle to a icon
  REBARINFO rbi;              // Contains information that describes
                              // rebar control characteristics.
  HIMAGELIST himlRB;          // A handle to an image list
  REBARBANDINFO rbbi[2];      // Contains information that defines bands
                              // in the rebar control
  INITCOMMONCONTROLSEX iccex; // Carries information used to load rebar
                              // control classes

  // Initialize the INITCOMMONCONTROLSEX structure.
  iccex.dwSize = sizeof (INITCOMMONCONTROLSEX);

  // Load rebar and toolbar control classes.
  iccex.dwICC = ICC_COOL_CLASSES | ICC_BAR_CLASSES;

  // Registers rebar and toolbar control classes from the common control
  // dynamic-link library (DLL).
  InitCommonControlsEx (&iccex);

  // Create rebar control.
  dwStyle = WS_VISIBLE | WS_BORDER | WS_CHILD | WS_CLIPCHILDREN |
            WS_CLIPSIBLINGS | RBS_VARHEIGHT | RBS_BANDBORDERS |
            CCS_NODIVIDER | CCS_NOPARENTALIGN;

  if (!(hwndRB = CreateWindowEx (0,
                                 REBARCLASSNAME,
                                 NULL,
                                 dwStyle,
                                 0,
                                 0,
                                 CW_USEDEFAULT,
                                 100,
                                 hwnd,
                                 (HMENU)ID_REBAR,
                                 g_hInst,
                                 NULL)))
  {
    return NULL;
  }

  // Sets the characteristics of the rebar control.
  himlRB = ImageList_Create (32, 32, ILC_COLORDDB | ILC_MASK, 1, 0);
  hIcon = LoadIcon (g_hInst, MAKEINTRESOURCE (IDI_REBAR));
  ImageList_AddIcon (himlRB, hIcon);

  rbi.cbSize = sizeof (rbi);
  rbi.fMask = RBIM_IMAGELIST;
  rbi.himl = himlRB;

  if (!SendMessage (hwndRB, RB_SETBARINFO, 0, (LPARAM)&rbi))
    return NULL;

  // Creates a toolbar.
  dwStyle = WS_VISIBLE | WS_CHILD | TBSTYLE_TOOLTIPS |
            CCS_NOPARENTALIGN | CCS_NORESIZE;

  if (!(hwndTB = CreateToolbarEx (hwnd,
                                  dwStyle,
                                  (UINT) ID_TOOLBAR,
                                  NUMIMAGES,
                                  g_hInst,
                                  IDB_TOOLBAR,
                                  tbButton,
                                  sizeof (tbButton) / sizeof (TBBUTTON),
                                  BUTTONWIDTH,
                                  BUTTONHEIGHT,
                                  IMAGEWIDTH,
                                  IMAGEHEIGHT,
                                  sizeof (TBBUTTON))))
  {
    return NULL;
  }

  // Add tooltips to the toolbar.
  SendMessage (hwndTB, TB_SETTOOLTIPS, (WPARAM) NUMIMAGES,
               (LPARAM) szToolTips);

  // Retrieves the dimensions of the bounding rectangle of the toolbar.
  GetWindowRect (hwndTB, &rect);

  memset (&rbbi[0], 0, sizeof (rbbi[0]));
  rbbi[0].cbSize = sizeof (REBARBANDINFO);
  rbbi[0].fMask = RBBIM_SIZE | RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_ID
                  | RBBIM_STYLE | RBBIM_TEXT | RBBIM_BACKGROUND | 0;

  rbbi[0].cxMinChild = rect.right - rect.left + 2;
  rbbi[0].cyMinChild = rect.bottom - rect.top + 2;
  rbbi[0].cx = 250;
  rbbi[0].fStyle = RBBS_BREAK | RBBS_GRIPPERALWAYS;
  rbbi[0].wID = ID_TOOLBAR;
  rbbi[0].hwndChild = hwndTB;
  rbbi[0].lpText = TEXT("Toolbar");
  rbbi[0].hbmBack = LoadBitmap (g_hInst, MAKEINTRESOURCE (IDB_BKGRD));

  // Inserts the toolbar band in the rebar control.
  SendMessage (hwndRB, RB_INSERTBAND, (WPARAM)-1,
               (LPARAM) (LPREBARBANDINFO)&rbbi[0]);

  // Creates a combo box.
  dwStyle = WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_VSCROLL |
            WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
            CBS_AUTOHSCROLL | CBS_DROPDOWN;

  if (!(hwndCombo = CreateWindowEx (0,
                                    TEXT("combobox"),
                                    NULL,
                                    dwStyle,
                                    0, 0, 100, 200,
                                    hwndRB,
                                    (HMENU)ID_COMBOBOX,
                                    g_hInst,
                                    NULL)))
  {
    return NULL;
  }

  // Add 10 items to the combo box.
  for (index = 0; index < 10; index++)
  {
    wsprintf (szString, TEXT("Item %d"), index + 1);
    SendMessage (hwndCombo, CB_ADDSTRING, 0, (LPARAM) szString);
  }

  // Select the first item as default.
  SendMessage (hwndCombo, CB_SETCURSEL, (WPARAM)0, 0);

  // Retrieve the dimensions of the bounding rectangle of the combo box.
  GetWindowRect (hwndCombo, &rect);

  memset (&rbbi[1], 0, sizeof (rbbi[1]));
  rbbi[1].cbSize = sizeof (REBARBANDINFO);
  rbbi[1].fMask = RBBIM_SIZE | RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_ID
                  | RBBIM_STYLE | RBBIM_TEXT | RBBIM_BACKGROUND
                  | RBBIM_IMAGE | 0;

  rbbi[1].cxMinChild = rect.right - rect.left;
  rbbi[1].cyMinChild = rect.bottom - rect.top;
  rbbi[1].cx = 100;
  rbbi[1].fStyle = RBBS_CHILDEDGE | RBBS_FIXEDBMP | 0;
  rbbi[1].wID = ID_COMBOBOX;
  rbbi[1].hwndChild = hwndCombo;
  rbbi[1].lpText = TEXT("ComboBox");
  rbbi[1].hbmBack = LoadBitmap (g_hInst, MAKEINTRESOURCE (IDB_BKGRD));
  rbbi[1].iImage = 0;

  // Inserts the combobox band in the rebar control.
  SendMessage (hwndRB, RB_INSERTBAND, (WPARAM)-1,
               (LPARAM) (LPREBARBANDINFO)&rbbi[1]);

  // Reposition the rebar control.
  MoveRebar (hwnd, hwndRB);

  return hwndRB;
}


Creating a Toolbar

A toolbar is a control that contains buttons. The buttons in a toolbar usually correspond to items on the application menu, providing a quick way for the user to access these commands. Toolbar buttons are bit images, not child windows as other buttons are. When a user taps a toolbar button, the toolbar sends its parent window a WM_COMMAND message with the button’s command identifier.

Each button in a toolbar can include a bitmap image. A toolbar maintains an internal list that contains all of the bitmaps assigned to each of its toolbar buttons. When you call the CreateToolbarEx function, you specify a monochrome or color bitmap that contains the initial images. The toolbar then adds the information to the internal list of images. You can add additional images later by using the TB_ADDBITMAP message.

Each image has a zero-based index. The first image added to the internal list has an index of zero, the second image has an index of one, and so on. TB_ADDBITMAP adds images to the end of the list and returns the index of the first new image that it added. You use an image index to associate the image with a button.

Windows CE assumes all toolbar bitmaps are the same size. You specify the size when you create the toolbar by calling CreateToolbarEx. If you call the CreateWindowEx function to create a toolbar, the size of its bitmaps is set to the default dimensions of 16 x 15 pixels. You can use the TB_SETBITMAPSIZE message to change the dimensions of the bitmaps, but you must do so before adding any images to the internal list of images.

Each button can display a string in addition to, or instead of, an image. A toolbar maintains an internal list that contains all of the strings available to toolbar buttons. You add strings to the internal list by using the TB_ADDSTRING message, specifying the address of the buffer containing the strings to add. Each string must be null-terminated, and the last string must be terminated with two null characters.

Each string has a zero-based index. The first string added to the internal list of strings has an index of zero, the second string has an index of one, and so on. TB_ADDSTRING adds strings to the end of the list and returns the index of the first new string. You use a string’s index to associate the string with a button.

Each button in a toolbar has a current state that indicates whether the button is hidden or visible, enabled or disabled, and pressed or not pressed. You set a button’s initial state when adding the button to the toolbar, and the toolbar updates the button state in response to a user’s actions, for example, when the user taps it with the stylus. You can use the TB_GETSTATE and TB_SETSTATE messages to retrieve and set the state of a button.

The following table shows toolbar button states supported by Windows CE.

State
Description
TBSTATE_CHECKED | The button has the TBSTYLE_CHECKED style and is pressed.
TBSTATE_ELLIPSES | The button displays ellipses if the text does not fit the size of the button. This style is unique to Windows CE.
TBSTATE_ENABLED | The button accepts user input. A button without this state does not accept user input and is dimmed.
TBSTATE_HIDDEN | The button is not visible and cannot receive user input.
TBSTATE_HIGHLIGHTED | The button is highlighted.
TBSTATE_INDETERMINATE | The button is dimmed.
TBSTATE_PRESSED | The button is being pressed.
TBSTATE_WRAP | The button has a line break following it. The button must also have the TBSTATE_ENABLED state.

To create a toolbar

  • Use the CreateToolbarEx function, which has the following syntax:
    HWND CreateToolbarEx(
    HWND hwnd,
    DWORD ws,
    UINT wID,
    int nBitmaps,
    HINSTANCE hBMInst,
    UINT wBMID,
    LPCTBBUTTON lpButtons,
    int iNumButtons,
    int dxButton,
    int dyButton,
    int dxBitmap,
    int dyBitmap,
    UINT uStructSize
);

    Here, hwnd is the handle to the parent window that owns the toolbar and ws is the style of the toolbar. Minimally, a toolbar must include the WS_CHILD style. You can also specify other styles. For example, in Windows CE, the TBSTYLE_LIST style creates a toolbar with variable width buttons. If you want to use the TBSTYLE_LIST style with fixed width buttons, you can override the default behavior by sending a TB_SETBUTTONSIZE or TB_SETBUTTONWIDTH message. To keep a toolbar from automatically aligning to the top or bottom of a parent window, specify the CCS_NOPARENTALIGN style. For a complete listing of supported styles, see Window and Control Styles

    The identifier associated with the toolbar is specified in wID and the number of button images contained in the bitmap specified by hBMInst and wBMID is specified in nBitmaps.

    Information about each button is contained in an array of structures called TBBUTTON; lpButtons is the address of that array. Specific information about buttons, such as the number of button to add to the toolbar, button height and width, and the height and width of button images are specified in iNumButtons, dxButton, dyButton, dxBitmap, and dyBitmap, respectively. The size of the TBBUTTON structure is specified in uStructSize.

Windows CE toolbar

The following code example shows how to create and register a toolbar.

HWND WINAPI CreateToolbar (HWND hwnd)
{
  int iCBHeight;              // Commandbar height
  DWORD dwStyle;              // Style of the toolbar
  HWND hwndTB = NULL;         // Handle of the command bar control
  RECT rect,                  // Contains the coordinates of the main
                              // window's client area
       rectTB;                // Contains the dimensions of the bounding
                              // rectangle of the toolbar control
  INITCOMMONCONTROLSEX iccex; // INITCOMMONCONTROLSEX structure

  iccex.dwSize = sizeof (INITCOMMONCONTROLSEX);
  iccex.dwICC = ICC_BAR_CLASSES;

  // Registers toolbar control classes from the common control
  // dynamic-link library (DLL).
  InitCommonControlsEx (&iccex);

  //  Create the toolbar control.
  dwStyle = WS_VISIBLE | WS_CHILD | TBSTYLE_TOOLTIPS |
            CCS_NOPARENTALIGN;

  if (!(hwndTB = CreateToolbarEx (
                  hwnd,               // Parent window handle
                  dwStyle,            // Toolbar window styles
                  (UINT) ID_TOOLBAR,  // Toolbar control identifier
                  NUMIMAGES,          // Number of button images
                  hInst,              // Module instance
                  IDB_TOOLBAR,        // Bitmap resource identifier
                  tbButton,           // Array of TBBUTTON structure
                                      // contains button information
                  sizeof (tbButton) / sizeof (TBBUTTON),
                                      // Number of buttons in toolbar
                  BUTTONWIDTH,        // Width of the button in pixels
                  BUTTONHEIGHT,       // Height of the button in pixels
                  IMAGEWIDTH,         // Button image width in pixels
                  IMAGEHEIGHT,        // Button image height in pixels
                  sizeof (TBBUTTON))))// Size of a TBBUTTON structure
  {
    return NULL;
  }

  // Add tooltips to the toolbar.
  SendMessage (hwndTB, TB_SETTOOLTIPS, (WPARAM) NUMIMAGES,
               (LPARAM) szToolTips);

  // Reposition the toolbar.
  GetClientRect (hwnd, &rect);
  GetWindowRect (hwndTB, &rectTB);
  iCBHeight = CommandBar_Height (hwndCB);
  MoveWindow (hwndTB,
              0,
              iCBHeight - 2,
              rect.right - rect.left,
              rectTB.bottom - rectTB.top,
              TRUE);

  return hwndTB;
}

You can also call the CreateWindowEx function to create a toolbar. Using this method, however, creates a toolbar that initially contains no buttons. You can then add buttons to the toolbar by using the TB_ADDBUTTONS or TB_INSERTBUTTON message. You register the toolbar class by specifying the TOOLBARCLASSNAME window class. Windows CE registers the TOOLBARCLASSNAME class when it loads the common control DLL. You can call the InitCommonControls function to ensure that this DLL is loaded. To register the toolbar class using the InitCommonControlsEx function, specify the ICC_BAR_CLASSES flag as the dwICC member of the INITCOMMONCONTROLSEX structure you pass in the lpInitCtrls parameter.

If you use CreateWindowEx to create a toolbar, you must specify the WS_CHILD window style. CreateToolbarEx includes the WS_CHILD style by default. You must specify the initial parent window when creating the toolbar, but you can change the parent window after creation by using the TB_SETPARENT message.

Windows CE does not support user customization of toolbars or drag-and-drop operations for toolbars.


Specifying Toolbar Size, Position, and Appearance

The window procedure for a toolbar automatically sets the size and position of the toolbar window. The height is based on the height of the buttons in the toolbar. The width is the same as the width of the parent window’s client area. The CCS_TOP and CCS_BOTTOM common control styles determine whether the toolbar is positioned along the top or bottom of the client area. By default, a toolbar has the CCS_TOP style.

The toolbar window procedure automatically adjusts the size of the toolbar when it receives a WM_SIZE or TB_AUTOSIZE message. An application should send either of these messages when the size of the parent window changes or after sending a message that requires the size of the toolbar to be adjusted, for example, after sending the TB_SETBUTTONSIZE message.

Windows CE also supports messages that enable you to customize the look and behavior of toolbars and toolbar buttons.

Create transparent toolbars by specifying the TBSTYLE_FLAT or TBSTYLE_TRANSPARENT styles. If you give a toolbar the TBSTYLE_FLAT style, the toolbar displays its buttons, but the toolbar itself is transparent. If you give a toolbar the TBSTYLE_TRANSPARENT style, the client area shows through the buttons as well as through the underlying toolbar.

You can use image lists to customize the way a toolbar displays buttons in various states. You can set and retrieve image lists for toolbar buttons by using the TB_GETIMAGELIST and TB_SETIMAGELIST messages for buttons in their default unpressed state, and the TB_GETDISABLEDIMAGELIST and TB_SETDISABLEDIMAGELIST messages for buttons in their disabled state. Use the TB_LOADIMAGES message to load images into a toolbar image list.

Windows CE supports a toolbar button style called a drop-down button. When a user taps a button that has the TBSTYLE_DROPDOWN style, the toolbar sends a TBN_DROPDOWN notification to its parent window. The parent window usually responds by displaying a pop-up menu or list box under the drop-down button.

Because toolbars in Windows CE support the custom draw service, you have flexibility to customize a toolbar’s appearance. If a toolbar provides this service, it sends the new NM_CUSTOMDRAW notification at specific times during drawing operations. The lParam of the NM_CUSTOMDRAW notification is a pointer to an NMCUSTOMDRAW structure, which contains the information necessary to draw the customized toolbar. For information on the custom draw service, see “Using Custom Draw Services” later in this chapter.


Creating ToolTips

A ToolTip is a small, rectangular pop-up window that displays a brief description of a command bar button’s purpose when the user holds the stylus on the button for more than 0.5 second. If the user lifts the stylus from the screen while still positioned over the button, the button is activated. If the user moves the stylus away from the button before raising the stylus from the screen, the button is not activated.

ToolTip

Windows CE supports ToolTips only for command bar and toolbar buttons, and for command bar menus. It does not support ToolTips for the combo boxes in a command bar. To add ToolTips to a command bar, call the CommandBar_AddTooltips function.

The CommandBar_AddtoolTips function does not make a copy of the array of ToolTip strings you pass to it. It directly uses the memory address you pass to it in the lpToolTips parameter. Do not release the memory allocated for this array until the application exits.

To add a ToolTip to a toolbar, use the TB_SETTOOLTIPS message.

ToolTips usually display only the name of a button command, but can display the shortcut key for the command.


Creating a Header Control

A header control is a horizontal window usually positioned above columns of data. It is divided into partitions that correspond to the columns, and each partition contains the title for the column below it. The user can drag the dividers between the partitions to set the width of each column. A header can also perform an action, such as sorting the rows of data according to the values in a column the user selects.

A header control sends notification messages to its parent window when the user taps or double-taps an item, when the user drags an item divider, and when the item attributes change. The parent window receives the notifications in the form of WM_NOTIFY messages.

Windows CE supplies macros to send header control messages as well as to support the use of image lists, drag-and-drop features, and custom ordering of header control items.

To create a header control

  1. Specify WC_HEADER in the lpClassName parameter of the CreateWindowEx function. This class is registered when the common control DLL is loaded. Use the InitCommonControls function to ensure that this DLL is loaded.
  2. Specify a control style in the dwStyle parameter of the CreateWindowEx function.

    For a complete listing of supported styles, see Window and Control Styles

To register the header control class using the InitCommonControlsEx function

  • Specify the ICC_ LISTVIEW_CLASSES flag as the dwICC member of the INITCOMMONCONTROLSEX structure you pass in the lpInitCtrls parameter.

Windows CE header control


Setting Header Control Size and Position

Typically, you must set the size and position of a header control to fit within the boundaries of a particular rectangle, such as the client area of a window. By using the HDM_LAYOUT message, you can retrieve the appropriate size and position values from the header control.

When sending the HDM_LAYOUT message, you specify the address of an HDLAYOUT structure that contains the coordinates of the rectangle that the header control is to occupy and that provides a pointer to a WINDOWPOS structure. The control fills WINDOWPOS with size and position values appropriate for positioning the control along the top of the specified rectangle. The height value is the sum of the heights of the control’s horizontal borders and the average height of characters in the font currently selected into the control’s device context.


Adding Header Control Items

A header control typically has several header items that define the columns of the control. To add an item to a header control, send the HDM_INSERTITEM message to the control. The message includes the address of an HDITEM structure. This structure defines the properties of the header item. The following code example shows the HDITEM syntax.

typedef struct _HD_ITEM { hdiUINTmask;intcxy; LPSTRpszText; HBITMAPhbm; intcchTextMax; intfmt; LPARAMlParam; } HD_ITEM;

The fmt member of an item’s HDITEM structure can include either the HDF_STRING or HDF_BITMAP flag to indicate whether the control displays the item’s string or bitmap. If you want to display both a string and a bitmap, create an owner-drawn item by setting the fmt member to include the HDF_OWNERDRAW flag. You can combine a string and an image from an image list by combining the HDF_IMAGE and HDF_STRING flags.

The HDITEM structure also specifies formatting flags that tell the control whether to center, left-align, or right-align the string or bitmap in the item’s rectangle.

HDM_INSERTITEM returns the index of the newly added item. You can use the index in other messages to set properties or retrieve information about the item. To delete an item, use the HDM_DELETEITEM message, which specifies the index of the item to delete.

The HDM_SETITEM message sets the properties of an existing header item and the HDM_GETITEM message retrieves the current properties of an item. To retrieve a count of the items in a header control, use the HDM_GETITEMCOUNT message.

You can define individual items of a header control to be owner-drawn items. Using this technique gives you more control than you would otherwise have over the appearance of a header item.

Use the HDM_INSERTITEM message to insert a new owner-drawn item into a header control or the HDM_SETITEM message to change an existing item to an owner-drawn item. Both messages include the address of an HDITEM structure, which should have the fmt member set to the HDF_OWNERDRAW value.


Working with Advanced Header Control Features

Windows CE enables you to use image lists in header controls, as well as text and bitmaps. An image list is a collection of same-size images, such as bitmaps or icons.

You can use the HDM_SETIMAGELIST message to associate an image list with a header control. Use the HDM_GETIMAGELIST message to retrieve the handle of the image list associated with a header control. To display an image with a header control item, specify HDI_IMAGE as the mask member, HDF_IMAGE as the fmt member, and the zero-based index of an image in the list as the iImage member of the HDITEM structure you use to add the item to the header control.

Header controls support callback requests for text and images in header control items. To create a callback item, set the pszText member to LPSTR_TEXTCALLBACK, or the iImage member to I_IMAGECALLBACK, in the HDITEM structure you fill in when you add the item to the header control. This causes the header control to send the HDN_GETDISPINFO notification message when the item is about to be drawn. The lParam of the WM_NOTIFY message is a pointer to an NMHDDISPINFO structure. When the header control sends the notification, it sets the NMHDDISPINFO structure’s members to specify the type of information it needs in order to draw the item. Return the requested information to the header control by filling in the appropriate members of the structure. If you set the mask member to HDI_DI_SETITEM, the header control stores the information and does not request it again. Otherwise, the header control sends the NMHDDISPINFO notification each time the item is redrawn.

Header controls also support drag-and-drop features. To create a header control that supports drag-and-drop operations, specify the HDS_DRAGDROP style when you create the header control. You can also customize a header control’s drag-and-drop behavior by handling the HDN_BEGINDRAG and HDN_ENDDRAG notification messages and by sending HDM_CREATEDRAGIMAGE and HDM_SETHOTDIVIDER messages.

You can support custom ordering of items in a header control by setting the iOrder member in the HDITEM structure when you add an item to a header control and by using the HDM_GETORDERARRAY, HDM_SETORDERARRAY, and HDM_ORDERTOINDEX messages.

Header controls support the custom draw service, which gives you flexibility to customize a header control’s appearance. If a header control provides this service, it sends the NM_CUSTOMDRAW notification at specific times during drawing operations. The lParam of the NM_CUSTOMDRAW notification is a pointer to an NMCUSTOMDRAW structure, which contains the information necessary to draw the customized header control.


Creating an Image List

An image list is a collection of same-size images. You can create the images in a single wide bitmap or as individual bitmaps that you add to the list one at a time. Image lists manage images, but do not display the images directly. They can be used independently or in conjunction with list view and tree view controls.

There are two types of image lists, nonmasked and masked. A nonmasked image list consists of a color bitmap that contains one or more images. A masked image list consists of two bitmaps of equal size. The first is a color bitmap that contains the images, and the second is a monochrome bitmap that contains a series of masks—one for each image in the first bitmap.

Windows CE draws a nonmasked image by simply copying it into the target device context and drawing it over the existing background color of the device context. Windows CE draws a masked image by combining its bits with the bits of the mask, typically producing transparent areas in the bitmap where the background color of the target device context shows through.

Note Most Windows CE-based platforms do not support cursors except for the wait cursor. Therefore, image lists cannot contain cursors.

To create an image list

  1. Call the ImageList_Create function.

    For a nonmasked image list, this function creates a single bitmap large enough to hold a specified number of images of the specified dimensions. Then, it creates a screen-compatible device context and selects the bitmap into it. For a masked image list, the function creates two bitmaps and two screen-compatible device contexts. ImageList_Create selects the image bitmap into one device context and the mask bitmap into the other.

  2. Specify the initial number of images in the image list, as well as the number of images by which the list can grow.

    If you attempt to add more images than you initially specified, the image list automatically grows to accommodate the images.

If ImageList_Create succeeds, it returns a handle to the HIMAGELIST type. Use this handle in other image list functions to access the image list and manage the images. You can add and remove images, copy images from one image list to another, and merge images from two different image lists. When you no longer need an image list, destroy it by specifying its handle in a call to the ImageList_Destroy function.

Use the ImageList_Duplicate, ImageList_SetImageCount, and ImageList_RemoveAll functions to respectively copy, resize, or remove all images from an image list.

The IMAGELISTDRAWPARAMS structure, used with the ImageList_DrawIndirect function, contains information about how to draw an image from an image list, such as what part of the image to draw, the foreground and background colors, the style, and a raster operation (ROP) code specifying how to combine the image’s colors with the background colors.


Using Images in Image Lists

You can add icons or other bit images to an image list. To add bit images, specify the handles to two bitmaps in a call to the ImageList_Add function. The first bitmap contains one or more images to add to the image bitmap, and the second bitmap contains the masks to add to the mask bitmap. Windows CE ignores the second bitmap handle for nonmasked images; you can set it to NULL.

The ImageList_AddMasked function adds bit images to a masked image list. This function is similar to ImageList_Add, in which you do not specify a mask bitmap. Instead, you specify a color that the system combines with the image bitmap to automatically generate the masks. Windows CE changes each pixel of the specified color in the image bitmap to black and sets the corresponding bit in the mask to one. As a result, any pixel in the image that matches the specified color is transparent when the image is drawn.

The ImageList_AddIcon function adds an icon to an image list. If the image list is masked, ImageList_AddIcon adds the mask provided with the icon to the mask bitmap. If the image list is nonmasked, the mask for the icon is not used when drawing the image.

To create an icon based on an image and mask in an image list, use the ImageList_GetIcon function. The function returns the handle to the icon. ImageList_Add, ImageList_AddMasked, and ImageList_AddIcon assign an index to each image as it is added to an image list. When more than one image is added at a time, the functions return the index of the first image. The ImageList_Remove function removes an image from an image list.

The ImageList_Replace and ImageList_ReplaceIcon functions replace an image in an image list with a new image. ImageList_Replace replaces an image with a bit image and mask, and ImageList_ReplaceIcon replaces an image with an icon. Use the ImageList_Copy function to move or copy images within an image list.

The ImageList_Merge function merges two images, storing the new image in a new image list. The new image is created by drawing the second image transparently over the first. The mask for the new image is the result of performing a logical OR operation on the bits of the masks for the two original images.

The ImageList_GetImageInfo function fills an IMAGEINFO structure with information about a single image, including the handles of the image and mask bitmaps, the number of color planes and bits per pixel, and the bounding rectangle of the image within the image bitmap. Use this information to directly manipulate the bitmaps for the image. The ImageList_GetImageCount function retrieves the number of images in an image list.

Use the ImageList_DrawIndirect function to specify custom drawing properties for an image in an image list. This function takes a pointer to an IMAGELISTDRAWPARAMS structure as a parameter. The IMAGELISTDRAWPARAMS structure contains information about how to draw the image.


Using Overlays in Image Lists

Every image list includes a list of indexes to use as overlays. An overlay is an image drawn transparently over another image. Any image currently in the image list can be used as an overlay. You can specify up to four overlays for each image list.

Add the index of an image to the list of overlays by using the ImageList_SetOverlayImage function, specifying the handle to the image list, the index of the existing image, and the desired overlay index. The overlay indexes are one-based rather than zero-based because an overlay index of zero means that no overlay will be used.

Specify an overlay when drawing an image with the ImageList_Draw or ImageList_DrawEx function. The overlay is specified by performing a logical OR operation between the desired drawing flags and the result of the INDEXTOOVERLAYMASK macro. The INDEXTOOVERLAYMASK macro formats the overlay index for inclusion with the flags for these functions.


Creating a List View Control

A list view is a common control that displays a collection of items, such as files or folders. Each item has an icon and a label. A user can choose whether to have the items displayed as large icons, small icons, a list, or a detailed list. You can design list views so that a user can drag an item to a new location within the list view or sort the collection by tapping a column header.

Image list in list view

To create a list view control

  1. Specify the WC_LISTVIEW class in the lpClassName parameter of the CreateWindowEx function.

    This class is registered when the common control DLL is loaded. Use the InitCommonControls function to ensure that this DLL is loaded. To register the list view class using the InitCommonControlsEx function, specify the ICC_LISTVIEW_CLASSES flag as the dwICC member of the INITCOMMONCONTROLSEX structure you pass in the lpInitCtrls parameter.

  2. Specify a list view style in the dwStyle parameter of the CreateWindowEx function.

You can speed up the creation of large list views by disabling the painting of the list view before adding the items. You do this by sending a WM_SETREDRAW message with the redraw flag in wParam set to FALSE. When you are finished adding items, re-enable painting by sending a WM_SETREDRAW message with the redraw flag wParam set to TRUE. Before inserting items, send the LVM_SETITEMCOUNT message with the cItems parameter set to the number of items in question. When you do this, the list view will allocate the memory it needs all at once, instead of having to reallocate more memory incrementally as the internal data structures grow.

You can change the view type after a list view control is created. To retrieve and change the window style, use the GetWindowLong and SetWindowLong functions. To determine the window styles that correspond to the current view, use the LVS_TYPEMASK value.

You can control the way items are arranged in icon view or small icon view by specifying either the LVS_ALIGNTOP windows style, which is the default, or the LVS_ALIGNLEFT window style. You can change the alignment after a list view control is created. To isolate the window styles that specify the alignment of items, use the LVS_ALIGNMASK value.

Windows CE does not support hot tracking, hover selection, background images, or list view ToolTips.


Creating Image Lists

By default, a list view control does not display item images. To display item images, you must create image lists and associate them with the control. A list view control can have three image lists:

  • One that contains full-sized icons displayed when the control is in icon view
  • One that contains small icons displayed when the control is in small icon view, list view, or report view
  • One that contains state images displayed to the left of the full-sized icon or small icon

You can use state images, such as checked or cleared check boxes, to indicate application-defined item states. State images are displayed in icon view, small icon view, list view, or report view.

To assign an image list to a list view control, use the LVM_SETIMAGELIST message to specify whether the image list contains full-sized icons, small icons, or state images. To retrieve the handle to an image list currently assigned to a list view control, use the LVM_GETIMAGELIST message. You can use the GetSystemMetrics function to determine appropriate dimensions for the full-sized icons and small icons. Use the ImageList_Create function to create an image list, and use other image list functions to add bitmaps to the image list.

Create only the image list that the control will use. For example, if the list view control will never be in icon view, do not create and assign a large image list because the large images will never be used. If you create large and small icon image lists, each image list must contain the same images in the same order. This is because a single value is used to identify a list view item’s icon in both image lists. You can associate an icon index with an item when you call the ListView_InsertItem or ListView_SetItem macro.

The full-sized icon and small icon image lists can also contain overlay images, designed to be drawn transparently over the item icons.

To use overlay images in a list view control

  1. Call the ImageList_SetOverlayImage function to assign an overlay image index to an image in the full-sized icon and small icon image lists.

    An overlay image is identified by a one-based index.

  2. Call the ListView_InsertItem or ListView_SetItem macro to associate an overlay image index with an item.
  3. Use the INDEXTOOVERLAYMASK macro to specify an overlay image index in the state member of the item’s LVITEM structure.

    You must also set the LVIS_OVERLAYMASK bits in the stateMask member.

To associate a state image with an item, use the INDEXTOSTATEIMAGEMASK macro to specify a state image index in the state member of the LVITEM structure.

By default, when a list view control is destroyed, it destroys the image lists assigned to it. However, if a list view control has the LVS_SHAREIMAGELISTS window style, you are responsible for destroying the image lists when no longer in use. You should specify this style if you assign the same image lists to multiple list view controls; otherwise, more than one control might try to destroy the same image list.

The following code example shows how to create a listview control and accompanying image list. It also shows how to assign the image list to the control.

HWND CreateListView (HINSTANCE hInstance, HWND hwndParent)
{
  DWORD dwStyle;              // The window style of list view control
  HWND hwndListView;          // The handle of list view control
  HIMAGELIST himlSmall;       // The handle to the small image list
  HIMAGELIST himlLarge;       // The handle to the large image list
  INITCOMMONCONTROLSEX iccex; // INITCOMMONCONTROLSEX structure

  // Initialize the INITCOMMONCONTROLSEX structure.
  iccex.dwSize = sizeof (INITCOMMONCONTROLSEX);

  // Load list view and header control classes.
  iccex.dwICC = ICC_LISTVIEW_CLASSES;

  // Registers treeview control classes from the common control
  // dynamic-link library (DLL).
  InitCommonControlsEx (&iccex);

  // Assign the list view window style.
  dwStyle = WS_TABSTOP | WS_CHILD | WS_BORDER | WS_VISIBLE |
            LVS_AUTOARRANGE | LVS_REPORT | LVS_OWNERDATA;

  // Create list view control.
  hwndListView = CreateWindowEx (
                  WS_EX_CLIENTEDGE,   // Extended window style
                  WC_LISTVIEW,        // Class name
                  TEXT(""),           // Window name
                  dwStyle,            // Window style
                  0,                  // Horizontal position of window
                  0,                  // Vertical position of window
                  0,                  // Window width
                  0,                  // Window height
                  hwndParent,         // Handle to parent window
                  (HMENU)ID_LISTVIEW, // Handle to menu identifier
                  g_hInst,            // Handle to application instance
                  NULL);              // Window-creation data

  // If it fails in creating the window, return.
  if (!hwndListView)
    return NULL;

  // Insert code to resize the list view window here since the list view
  // control was created zero in size.
  // ...

  // Create the large and small image lists.
  himlSmall = ImageList_Create (16, 16, ILC_COLORDDB | ILC_MASK, 1, 0);
  himlLarge = ImageList_Create (32, 32, ILC_COLORDDB | ILC_MASK, 1, 0);

  if (himlSmall && himlLarge)
  {
    HICON hIcon;

    // Load the small icon from the instance.
    hIcon = LoadImage (g_hInst, MAKEINTRESOURCE (IDI_DISK), IMAGE_ICON,
                       16, 16, LR_DEFAULTCOLOR);

    // Adds the icon to the image list.
    ImageList_AddIcon (himlSmall, hIcon);

    // Load the small icon from the instance.
    hIcon = LoadIcon (g_hInst, MAKEINTRESOURCE (IDI_DISK));

    // Adds the icon to the image list.
    ImageList_AddIcon (himlLarge, hIcon);

    // Assigns the large and small image lists to the list view control.
    ListView_SetImageList (hwndListView, himlSmall, LVSIL_SMALL);
    ListView_SetImageList (hwndListView, himlLarge, LVSIL_NORMAL);
  }

  return hwndListView;
}


Adding Items and Subitems

Each item in a list view control has an icon, a label, a current state, and an application-defined value. By using list view messages, you can add, modify, and delete items as well as retrieve information about items. You can also find items with specific attributes.

Each item can also have one or more subitem. A subitem is a string that, in report view, is displayed in a column to the right of an item’s icon and label. To specify the text of a subitem, use the LVM_SETITEMTEXT or LVM_SETITEM message. All items in a list view control have the same number of subitems. The number of subitems is determined by the number of columns in the list view control.

The LVITEM structure defines a list view item or subitem. To add an item to a list view control, use the LVM_INSERTITEM message. Before adding multiple items, you can send the control an LVM_SETITEMCOUNT message to specify the number of items the control will ultimately contain. This message enables the list view control to reallocate its internal data structures only once rather than every time you add an item. Determine the number of items in a list view control by using the LVM_GETITEMCOUNT message.

Use the LVM_SETITEM message to change the attributes of a list view item. The LVM_SETITEMTEXT message only changes the text of an item or subitem.

To retrieve information about a list view item, use the LVM_GETITEM message specifying the address of the LVITEM structure to fill in. To retrieve only an item or subitem’s text, use the LVM_GETITEMTEXT message. To delete a list view item, use the LVM_DELETEITEM message. Delete all items in a list view control by using the LVM_DELETEALLITEMS message.


Adding Callback Items and Callback Masks

For each of its items, a list view control typically stores the label text, the image list index of the item’s icons, and a set of bit flags for the item state. A callback item in a list view control is an item for which the application stores the text, icon index, or both. You can define callback items or change the control’s callback mask to indicate that the application—rather than the control—stores some or all of this data. You may want to use callbacks if your application already stores some of this data. You can define callback items when you send the LVM_INSERTITEM message to add an item to the list view control.

The callback mask of a list view control is a set of bit flags that specify the item states for which the application, rather than the control, stores the current data. The callback mask applies to all of the control items, unlike the callback item designation, which applies to a specific item. The callback mask is zero by default, meaning that the list view control stores all item-state data. After creating a list view control and initializing its items, you can send the LVM_SETCALLBACKMASK message to change the callback mask. To get the current callback mask, send the LVM_GETCALLBACKMASK message.

When a list view control must display or sort a list view item for which the application stores callback data, the control sends the LVN_GETDISPINFO notification message to the control’s parent window. This message specifies an NMLVDISPINFO structure that indicates the type of data required. The parent window must process LVN_GETDISPINFO to provide the requested data.

If the list view control detects a change in an item’s callback data, the control sends an LVN_SETDISPINFO notification message to notify you of the change. Changes that the list view control detect are alterations to the text, the icon, or the state data being tracked by the application.

The following code example shows how the listview control requests data.

LRESULT ListViewNotify (HWND hwnd, LPARAM lParam)
{
  LPNMHDR lpnmh = (LPNMHDR) lParam; // Contains information about the
                                    // notification message.
  HWND hwndListView  = GetDlgItem (hwnd, ID_LISTVIEW);
                                    // Handle of the list view control
  switch (lpnmh->code)
  {
    case LVN_GETDISPINFO:
    {
      TCHAR szString[MAX_PATH];
      LV_DISPINFO *lpdi = (LV_DISPINFO *) lParam;

      // The message LVN_GETDISPINFO is sent by the list view control to
      // its parent window. It is a request for the parent window to
      // provide information needed to display or sort a list view item.

      return 0;
    }

    case LVN_ODCACHEHINT:
    {
      LPNMLVCACHEHINT lpCacheHint = (LPNMLVCACHEHINT)lParam;

      // The message LVN_ODCACHEHINT is sent by the list view control
      // when the contents of its display area have changed. For
      // example, a list view control sends this notification when the
      // user scrolls the control's display.

      return 0;
    }

    case LVN_ODFINDITEM:
    {
      LPNMLVFINDITEM lpFindItem = (LPNMLVFINDITEM)lParam;

      // The message LVN_ODFINDITEM is sent by the list view control
      // when it needs the owner to find a particular callback item.
      // Return -1 if the item is not found.

      return 0;
    }
  }

If you change a callback item’s attributes or state bits, you can use the LVM_UPDATE message to force the control to repaint the item. This message also causes the control to arrange its items if it has the LVS_AUTOARRANGE style. You can use the LVM_REDRAWITEMS message to redraw a range of items by invalidating the corresponding portions of the list view control’s client area.


Adding Columns

Columns control the way items and their subitems are displayed in report view. Each column has a title and width and is associated with a specific subitem. The attributes of a column are defined by an LVCOLUMN structure.

To add a column to a list view control, use the LVM_INSERTCOLUMN message. To delete a column, use the LVM_DELETECOLUMN message. You can retrieve and change the properties of an existing column by using the LVM_GETCOLUMN and LVM_SETCOLUMN messages. To retrieve or change a column width, use the LVM_GETCOLUMNWIDTH and LVM_SETCOLUMNWIDTH messages.

Unless the LVS_NOCOLUMNHEADER window style is specified, column headers appear in report view. The user can tap a column header, which causes the live view control to send an LVN_COLUMNCLICK notification message to the parent window. Typically, the parent window sorts the list view control by the specified column when the user taps the column header.

List view controls can set the order in which columns are displayed. To implement this feature, specify the LVCF_ORDER value and assign the proper value to the iOrder member in the LVCOLUMN structure.

The following code example shows how to add columns and set the number of items in the listview window.

BOOL InitListView (HWND hwndListView)
{
  int index;
  LV_COLUMN lvColumn;
  TCHAR szString[5][20] = {TEXT("Main Column"),
                           TEXT("Column 1"),
                           TEXT("Column 2"),
                           TEXT("Column 3"),
                           TEXT("Column 4")};

  // Empty the list in list view.
  ListView_DeleteAllItems (hwndListView);

  // Initialize the columns in the list view.
  lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
  lvColumn.fmt = LVCFMT_LEFT;
  lvColumn.cx = 120;

  // Insert the five columns in the list view.
  for (index = 0; index < 5; index++)
  {
    lvColumn.pszText = szString[index];
    ListView_InsertColumn (hwndListView, index, &lvColumn);
  }

  // Set the number of items in the list to ITEM_COUNT.
  ListView_SetItemCount (hwndListView, ITEM_COUNT);

  return TRUE;
}


Arranging, Sorting, and Finding List Views

You can use list view messages to arrange and sort items and to find items based on their attributes or position. Although arranging items repositions them to align on a grid, the indexes of the items do not change. Sorting changes the sequence of items and their corresponding indexes, and then repositions them in the order specified. You can arrange items only in icon and small icon views, but you can sort items in any view.

To arrange items, use the LVM_ARRANGE message. You can ensure that items are arranged at all times by specifying the LVS_AUTOARRANGE window style.

To sort items, use the LVM_SORTITEMS message. When you sort using this message, you specify an application-defined callback function that the list view control calls to compare the relative order of any two items. By specifying the appropriate item data and supplying an appropriate comparison function, you can sort items by their labels, by any subitems, or by any other properties. Note that sorting items does not reorder the corresponding subitems. Thus, if any subitems are not callback items, you must regenerate the subitems after sorting.

Ensure that a list view control is always sorted by specifying the LVS_SORTASCENDING or LVS_SORTDESCENDING window style. Controls with these styles use the label text of the items to sort them in ascending or descending order. You cannot supply a comparison function when using these window styles.

You can find a list view item with specific properties by using the LVM_FINDITEM message. Use the LVM_GETNEXTITEM message to find a list view item in a specified state and bears a specified geometrical relationship to a specified item.


Setting List View Item and Scroll Position

Every list view item has a position and size, which you can retrieve and set using messages. You can also determine which item, if any, is at a specified position. The position of list view items is specified in view coordinates, which are client coordinates offset by the scroll position.

To retrieve and set an item’s position, use the LVM_GETITEMPOSITION and LVM_SETITEMPOSITION messages, respectively. LVM_GETITEMPOSITION works for all views, but LVM_SETITEMPOSITION works only for icon and small icon views.

You can determine which item, if any, is at a particular location by using the LVM_HITTEST message. To get the bounding rectangle for a list item, or for only its icon or label, use the LVM_GETITEMRECT message.

Unless the LVS_NOSCROLL window style is specified, you can use messages to perform a variety of scrolling operations. You can scroll a list view control to show items that do not fit in the client area of the control, determine a list view control’s scroll position, scroll a list view control by a specified amount, or scroll a list view control so that a specified list item is visible.

In icon view or small icon view, the current scroll position is defined by the view origin. The view origin is the set of coordinates, relative to the visible area of the list view control, that correspond to the view coordinates (0, 0). To get the current view origin, use the LVM_GETORIGIN message. This message should be used only in icon or small icon view; it returns an error in list or report view.

In list or report view, the current scroll position is defined by the top index. The top index is the index of the first visible item in the list view control. To get the current top index, use the LVM_GETTOPINDEX message. This message returns a valid result only in list view or report view; it returns zero in icon or small icon view.

Use the LVM_GETVIEWRECT message to get the bounding rectangle of all items in a list view control relative to the visible area of the control.

The LVM_GETCOUNTPERPAGE message returns the number of items that fit in one page of the list view control. This message returns a valid result only in list and report views; in icon and small icon views, it returns the total number of items.

To scroll a list view control by a specific amount, use the LVM_SCROLL message. Using the LVM_ENSUREVISIBLE message, you can scroll the list view control, if necessary, to ensure that a specified item is visible.


Editing Labels

A list view control that has the LVS_EDITLABELS window style enables a user to edit item labels in place. The user begins editing by clicking the label of an item that has the focus. An application can begin editing automatically by using the LVM_EDITLABEL message. The list view control notifies the parent window when editing begins, is canceled, or completed. When editing is completed, the parent window is responsible for updating the item label, if appropriate.

When label editing begins, a list view control sends its parent window an LVN_BEGINLABELEDIT notification message. You can process this message to enable selective editing of specific labels; returning a nonzero value prevents label editing.

When label editing is canceled or completed, a list view control sends its parent window an LVN_ENDLABELEDIT notification message. The parent window is responsible for updating the item label if it keeps the new label.

During label editing, you can get the handle to the edit control used for label editing by using the LVM_GETEDITCONTROL message. To limit the amount of text a user can type, you can send the edit control an EM_LIMITTEXT message. You can even subclass the edit control to intercept and discard invalid characters.


Using Advanced List View Features

In Windows CE, you can set the order of the columns that display in report view by setting the iOrder member in the LVCOLUMN structure when you add a column to a list view control. You can also set the column order by using the LVM_GETCOLUMNORDERARRAY and LVM_SETCOLUMNORDERARRAY messages.

To display an image from an image list next to the title of a column in report view, specify LVCF_IMAGE in the mask member and LVCFMT_IMAGE in the fmt member. When you add a column to a list view control, specify the zero-based index of an image in the list in the iImage member of LVCOLUMN.

List view controls in Windows CE support a custom draw service, which gives you flexibility to customize a list view’s appearance. If a list view provides this service, it sends the NM_CUSTOMDRAW notification at specific times during drawing operations.

Windows CE supports a list view style, LVS_OWNERDATA, for creating a virtual list view. The only data that a virtual list view manages is input focus and item selection data. All other data is managed by the owner of the list view. This enables a list view to handle very large data sets, especially in cases where the data is stored in a database that has its own data access methods.


Creating a Trackbar

A trackbar, also known as a slider control, is a common control that consists of a bar with tick marks on it and a slider, also known as a thumb. When a user drags the slider or clicks on either side of it, the slider moves in the appropriate direction, in one tick increments.

Window CE trackbar

To create a trackbar

  1. Specify TRACKBAR_CLASS in the lpClassName parameter to the CreateWindowEx function.

    This class is registered when the common control DLL is loaded. You can use the InitCommonControls function to ensure that this DLL is loaded. To register the trackbar class using the InitCommonControlsEx function, specify the ICC_BAR_CLASSES flag as the dwICC member of the INITCOMMONCONTROLSEX structure you pass in the lpInitCtrls parameter.

  2. Specify a trackbar style using the dwStyle parameter of the CreateWindowEx function.

    For a complete listing of supported styles, see Window and Control Styles

You can send messages to the trackbar to retrieve data about the window and to change its characteristics.

To retrieve the position of the slider, which is the value that the user has chosen, use the TBM_GETPOS message. To set the position of the slider, use the TBM_SETPOS message.

The range of a trackbar is the set of contiguous values that the trackbar can represent. Use the TBM_SETRANGE message to set the range of a trackbar when it is first created. You can dynamically alter the range by using the TBM_SETRANGEMAX and TBM_SETRANGEMIN messages. An application that accepts dynamic range changes retrieves the final range settings when the user has finished working with the trackbar. To retrieve these settings, use the TBM_GETRANGEMAX and TBM_GETRANGEMIN messages.

A trackbar automatically displays tick marks at each end, unless you specify the TBS_NOTICKS style. Use the TBS_AUTOTICKS style to automatically display additional tick marks at regular intervals along the trackbar. By default, a TBS_AUTOTICKS trackbar displays a tick mark at each increment of the trackbar’s range. To specify a different interval for the automatic tick marks, send the TBM_SETTICFREQ message to the trackbar.

To set the position of a single tick mark, send the TBM_SETTIC message. A trackbar maintains an array of DWORD values that stores the position of each tick mark. The array does not include the first and last tick marks that the trackbar creates automatically. You can specify an index in this array when you send the TBM_GETTIC message to get the position of the corresponding tick mark. Alternatively, you can send the TBM_GETPTICS message to get a pointer to the array. To retrieve the physical position of a tick mark, send the TBM_GETTICPOS message. The TBM_CLEARTICS message removes all but the first and last of a trackbar’s tick marks.

The trackbar line size determines how far the slider moves in response to keyboard input from the arrow keys, such as the RIGHT ARROW or DOWN ARROW key. To retrieve or set the line size, send the TBM_GETLINESIZE and TBM_SETLINESIZE messages, respectively.

The trackbar page size determines how far the slider moves in response to keyboard input, such as the PAGE UP or PAGE DOWN key, or mouse input, such as clicks in the trackbar channel. To retrieve or set the page size, send the TBM_GETPAGESIZE and TBM_SETPAGESIZE messages.

An application can send messages to retrieve the dimensions of a trackbar. The TBM_GETTHUMBRECT message retrieves the bounding rectangle for the slider. The TBM_GETTHUMBLENGTH message retrieves the length of the slider. The TBM_GETCHANNELRECT message retrieves the bounding rectangle for the trackbar’s channel, which is the area over which the slider moves. If a trackbar has the TBS_FIXEDLENGTH style, you can send the TBM_SETTHUMBLENGTH message to change the length of the slider.

A trackbar with the TBS_ENABLESELRANGE style can indicate a selection range by highlighting a range of the trackbar channel and displaying triangular tick marks at the start and end of the selection. When a trackbar has this style, you can send messages to set and retrieve the selection range. Typically, an application handles the trackbar notification messages and sets the trackbar’s selection range according to user input. The TBM_SETSEL message sets the starting and ending positions of a selection. To set just the starting position or just the ending position of a selection, use the TBM_SETSELSTART or TBM_SETSELEND message. To retrieve the starting and ending positions of a selection range, send the TBM_GETSELSTART and TBM_GETSELEND messages. To clear a selection range, send the TBM_CLEARSEL message.


Creating a Tree View

A tree view control is a hierarchical display of labeled items. Any item in a tree view control can have a list of subitems—called child items—associated with it. An item that has one or more child items is called a parent item. A child item is displayed below its parent item and is indented to indicate that it is subordinate to the parent. An item that has no parent appears at the top of the hierarchy and is called a root item.

To create a treeview control

  1. Specifying the WC_ TREEVIEW class in the lpClassName parameter of the CreateWindowEx function.

    This class is registered when the common control DLL is loaded. You can use the InitCommonControls function to ensure that this DLL is loaded. To register the tree view class using the InitCommonControlsEx function, specify the ICC_ TREEVIEW_CLASSES flag as the dwICC member of the INITCOMMONCONTROLSEX structure you pass in the lpInitCtrls parameter.

  2. Specify a treeview style in the dwStyle parameter of the CreateWindowEx function.

    Tree view styles govern aspects of a tree view control’s appearance. You set the initial styles when you create the tree view control. You can retrieve and change the styles after creating the tree view control by using the GetWindowLong and SetWindowLong functions. For a complete listing of supported styles, see Window and Control Styles

  3. Add one or more items to the treeview control by sending the TVM_INSERTITEM message.

    The message returns a handle to the HTREEITEM type, which uniquely identifies the item. The message also includes a TVINSERTSTRUCT structure that specifies the handle to the parent item and the handle to the item after which the new item is to be inserted. When adding an item, specify the handle to the new item’s parent item. If you specify NULL or the TVI_ROOT value instead of a parent item handle in the TVINSERTSTRUCT structure, the item is added as a root item. The second handle must identify either a child item of the specified parent or one of these values: TVI_FIRST, TVI_LAST, or TVI_SORT. When you specify TVI_FIRST or TVI_LAST, the tree view control places the new item at the beginning or end of the specified parent item’s list of child items. When you specify TVI_SORT, the tree view control inserts the new item into the list of child items in alphabetical order based on the text of the item labels. You can put a parent item’s list of child items in alphabetical order by using the TVM_SORTCHILDREN message. The TVM_SORTCHILDRENCB message enables you to sort child items based on criteria that you define. When you use this message, you specify an application-defined callback function that the tree view control can call when the relative order of two child items needs to be determined.

    Note A tree view control uses memory allocated from the heap of the process that creates the tree view control. The maximum number of items in a tree view is based on the amount of memory available in the heap.

At any time, the state of a parent item’s list of child items can be either expanded, partially expanded, or collapsed. When the state is expanded, the child items of the expanded section are displayed below the parent item. When it is collapsed, the child items are not displayed. The list automatically toggles between the expanded and collapsed states when the user double-taps the parent item or, if the parent has the TVS_HASBUTTONS style, when the user clicks the button associated with the parent item. You can expand or collapse the child items by using the TVM_EXPAND message. A tree view control sends the parent window a TVN_ITEMEXPANDING notification message when a parent item’s list of child items is about to be expanded or collapsed. The notification gives an application the opportunity to prevent the change or to set any attributes of the parent item that depend on the state of the list of child items. After changing the state of the list, the tree view control sends the parent window a TVN_ITEMEXPANDED notification message. When a list of child items is expanded, it is indented relative to the parent item. Set the amount of indentation by using the TVM_SETINDENT message or retrieve the current amount by using the TVM_GETINDENT message.

The following code example shows how to create a treeview control.

DWORD dwStyle;                // Style flags of the treeview
INITCOMMONCONTROLSEX  iccex;  // INITCOMMONCONTROLSEX structure

// Initialize the INITCOMMONCONTROLSEX structure.
iccex.dwSize = sizeof (INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_TREEVIEW_CLASSES;

// Registers treeview control classes from the common control
// dynamic-link library (DLL).
InitCommonControlsEx (&iccex);

// Get the client area rectangle.
GetClientRect (hwnd, &rcClient);

// Create the command bar and insert menu.
g_hwndCB = CommandBar_Create (g_hInst, hwnd, 1);
CommandBar_InsertMenubar (g_hwndCB, g_hInst, IDR_MENU, 0);
CommandBar_AddAdornments (g_hwndCB, 0, 0);

// Get the command bar height.
iCBHeight = CommandBar_Height (g_hwndCB);

// Assign the window styles for the treeview.
dwStyle = WS_VISIBLE | WS_CHILD | TVS_HASLINES | TVS_LINESATROOT |
          TVS_HASBUTTONS;

// Create the treeview control.
g_hwndTreeView = CreateWindowEx (
    0,
    WC_TREEVIEW,          // Class name
    TEXT("TreeView"),     // Window name
    dwStyle,              // Window style
    0,                    // x coordinate of the upper left corner
    iCBHeight + 1,        // y coordinate of the upper left corner
    rcClient.right,       // The width of the edit control window
    rcClient.bottom - (iCBHeight + 1),
                          // The height of the edit control window
    hwnd,                 // Window handle of parent window
    (HMENU) IDC_TREEVIEW, // The treeview control identifier
    g_hInst,              // The instance handle
    NULL);                // Specify NULL for this parameter when
                          // creating a control

// Make sure the treeview was actually created.
 if (!g_hwndTreeView)
  return 0;


Adding and Editing Item Labels

You typically specify the text of an item’s label when you add the item to the tree view control. The TVM_INSERTITEM message includes a TVITEM structure that defines the item’s properties, including a string containing the text of the label.

A tree view control allocates memory for storing each item; the text of the item labels takes up a significant portion of this memory. If you maintain a copy of the strings in the tree view control, you can decrease the memory requirements of the control by specifying the LPSTR_TEXTCALLBACK value in the pszText member of TVITEM instead of passing actual strings to the tree view. Using LPSTR_TEXTCALLBACK causes the tree view control to retrieve the text of an item’s label from the parent window when the item needs to be redrawn.

The user can directly edit the labels of items in a tree view control that has the TVS_EDITLABELS style. The user begins editing by clicking the label of the item that has the focus. An application begins editing by using the TVM_EDITLABEL message. The tree view control notifies the parent window when editing begins and when it is canceled or completed. When the user or application completes editing, the parent window is responsible for updating the item’s label, if appropriate.

When the user begins editing the label, a tree view control sends its parent window a TVN_BEGINLABELEDIT notification message. By processing this notification, an application can accept some label editing and reject editing others. Returning zero accepts editing, and returning nonzero rejects it.

When the user cancels or completes editing the label, a tree view control sends its parent window a TVN_ENDLABELEDIT notification message. The pszText member of TVITEM is zero if editing is canceled.


Modifying Tree View Item Appearance

Every tree view item has a current state that determines its appearance and features. You can retrieve and set this state by sending the TVM_GETITEM and TVM_SETITEM messages, or by using the TreeView_GetItem and TreeView_SetItem macros. You set or retrieve the item state by using the state member of the TV_ITEM structure that you pass in the pItem parameter (lParam) to these messages and macros.

Windows CE supports the TVIS_EXPANDPARTIAL item state. This state indicates that a tree view item is partially expanded. This could happen if an error occurs during data retrieval and some of the child items cannot be retrieved from the data source. The tree view displays the items that were successfully retrieved, but continues to display the plus symbol next to the parent item as well. This indicates to the user that more information is available. When the user clicks the plus symbol again, the application repeats the query.

The following table shows item states supported by Windows CE.

State
Definition
TVIS_BOLD | Windows CE uses a bold font to draw the item.
TVIS_CUT | Windows CE selects the item for cutting and pasting.
TVIS_DROPHILITED | Windows CE selects the item for dropping and dragging.
TVIS_EXPANDED | Windows CE expands the items list of child items so that the child items are visible. This state applies only to parent items.
TVIS_EXPANDEDONCE | Windows CE expands the item’s list of child items at least once. Windows CE does not send the TVN_ITEMEXPANDING and TVN_ITEMEXPANDED notifications for parent items that have specified this value. This value applies only to parent items.
TVIS_EXPANDPARTIAL | Windows CE partially expands the items. This could happen if an error occurs during data retrieval and some of the child items cannot be retrieved from the data source.
TVIS_FOCUSED | Windows CE gives the item the focus and surrounds it with a standard focus rectangle. Although more than one item can be selected, only one item can have the focus.
TVIS_OVERLAYMASK | Windows CE includes the item’s overlay image when it draws the image. The index of the overlay image must be specified in the state member of the TV_ITEM structure by using the INDEXTOOVERLAYMASK macro. The overlay image must be added to the tree view’s image list by using the ImageList_SetOverlayImage function. This value should not be combined with any other value.
TVIS_SELECTED | Windows CE selects the item. The appearance of a selected item depends on whether it has the focus and on whether the system colors are used for selection.
TVIS_STATEIMAGEMASK | Windows CE includes the item’s state image when it draws the item. The index of the state image must be specified in the state member of the TV_ITEM structure by using the INDEXTOSTATEIMAGEMASK macro. This value should not be combined with any other value.


Creating a Tree View Image List

Each item in a tree view control can have four bit images associated with it:

  • An image, such as an open folder, appears when the item is selected
  • An image, such as a closed folder, is displayed when the item is not selected
  • An overlay image drawn transparently over the selected or nonselected image
  • A state image, which is an additional image displayed to the left of the selected or nonselected image. You can use state images, such as checked and cleared check boxes, to indicate application-defined item states.

By default, a tree view control does not display item images. To display item images, you must create image lists and associate them with the control.

A tree view control can have two image lists: a normal image list and a state image list. A normal image list stores the selected, nonselected, and overlay images. A state image list stores state images.

To create an image list, call the ImageList_Create function, and use other image list functions to add bitmaps to the image list. Then, to associate the image list with the tree view control, use the TVM_SETIMAGELIST message. The TVM_GETIMAGELIST message retrieves a handle to one of a tree view control’s image lists.

In addition to the selected and nonselected images, a tree view control’s normal image list can contain up to four overlay images. Overlay images are designed to be drawn transparently over the selected and nonselected images. To assign an overlay mask index to an image in the normal image list, call the ImageList_SetOverlayImage function.

By default, all items display the first image in the normal image list for both the selected and nonselected states. Also, by default, items do not display overlay images or state images. You can change these default behaviors for an item by sending the TVM_INSERTITEM or TVM_SETITEM messages. These messages use the TVITEM structure to specify image list indexes for an item.

To associate an overlay image with an item, use the INDEXTOOVERLAYMASK macro to specify an overlay mask index in the state member of the item’s TVITEM structure. You must also set the TVIS_OVERLAYMASK bits in the stateMask member. Overlay mask indexes are one-based; an index of zero indicates that the application does not specify an overlay image.

To associate a state image with an item, use the INDEXTOSTATEIMAGEMASK macro to specify a state image index in the state member of the item’s TVITEM structure. The index identifies an image in the control’s state image list.

Note You can speed up the creation of large tree views by disabling the painting of the tree view before adding the items. You do this by sending a WM_SETREDRAW message with the redraw flag set to FALSE. When finished adding items, re-enable painting by sending a WM_SETREDRAW message with the redraw flag set to TRUE.

The following code example shows how to create and set an image list for a treeview control, then redraw the control using the new images.

BOOL InitTreeViewImageLists (HWND hwndTreeView)
{
  HBITMAP hBmp;          // Handle of the bitmaps to be added.

  // Create the image list for the item pictures.
  if ((g_hImgList = ImageList_Create (CX_BITMAP, CY_BITMAP, ILC_MASK,
                                      NUM_BITMAPS, 0)) == NULL)
    return FALSE;

  // Loads the bitmap resource.
  hBmp = LoadBitmap (g_hInst, MAKEINTRESOURCE (IDB_IMAGES));

  // Adds the images to the image list, generating a mask from the
  // bitmap.
  if (ImageList_AddMasked (g_hImgList, hBmp, RGB (0, 255, 0)) == -1)
  {
    return FALSE;
  }

  // Deletes the bitmap object and free system resources.
  DeleteObject (hBmp);

  // If not all the images were added then return.
  if (ImageList_GetImageCount (g_hImgList) < NUM_BITMAPS)
    return FALSE;

  // Sets the image list for the tree view control and redraws the
  // control by using the new images.
  TreeView_SetImageList (hwndTreeView, g_hImgList, TVSIL_NORMAL);

  return TRUE;
}


Handling Tree View Messages and Notifications

A tree view control notifies the parent window when the selection changes from one item to another by sending the TVN_SELCHANGING and TVN_SELCHANGED notification messages. The notifications also include data about the item that gains the selection and the item that loses the selection. You can use this data to set item attributes that depend on the selection state of the item. Returning TRUE in response to TVN_SELCHANGING rejects the selection change, and returning FALSE accepts the selection change. Change the selection by sending the TVM_SELECTITEM message.

Tree view controls support a number of messages that retrieve data about items in the control.

The TVM_GETITEM message can retrieve an item’s handle and attributes. An item’s attributes include its current state, the indexes in the control’s image list of the item’s selected and nonselected bit images, a flag that indicates whether the item has child items, the address of the item’s label string, and the item's application-defined 32-bit value.

The TVM_GETNEXTITEM message retrieves the tree view item that bears the specified relationship to the current item. The message can retrieve an item’s parent, the next or previous visible item, the first child item, and so on.

The TVM_GETITEMRECT message retrieves the bounding rectangle for a tree view item. The TVM_GETCOUNT and TVM_GETVISIBLECOUNT messages retrieve a count of the items in a tree view control and a count of the items that can be fully visible in the tree view control’s window, respectively. You can ensure that a particular item is visible by using the TVM_ENSUREVISIBLE message.


Handling Drag-and-Drop Operations

A tree view control notifies the parent window when the user starts to drag an item with a mouse. The parent window receives a TVN_BEGINDRAG notification message when the user begins dragging an item with the left mouse button and a TVN_BEGINRDRAG notification message when the user begins dragging with the right button. You can prevent a tree view control from sending these notifications by giving the tree view control the TVS_DISABLEDRAGDROP style.

You obtain an image to display during a drag operation by using the TVM_CREATEDRAGIMAGE message. The tree view control creates a dragging bitmap based on the label of the item being dragged. Then, the tree view control creates an image list, adds the bitmap to it, and returns the handle to the image list.

You must provide the code that actually drags the item. This typically involves using the dragging capabilities of the image list functions and including code for processing the WM_MOUSEMOVE and WM_LBUTTONUP messages sent to the parent window after the drag operation begins.

To use an item in a tree view control as the target of a drag-and-drop operation, use the SendMessage function to send a TVM_HITTEST message to determine when the stylus is on a target item. To do this, specify the address of a TVHITTESTINFO structure that contains the current coordinates of the stylus. When the SendMessage function returns, the structure contains a flag indicating the location of the stylus relative to the tree view control. If the stylus is over an item in the tree view control, the structure contains the handle to the item as well.

You indicate that an item is the target of a drag-and-drop operation by using the TVM_SETITEM message to set the state to TVIS_DROPHILITED. An item that has this state is drawn in the style used to indicate a target for a drag-and-drop operation.

The following code example shows how to handle drag and drop messages.

case WM_NOTIFY:
{
  LPNMHDR  pnmh = (LPNMHDR) lParam;

  switch (pnmh->code)
  {
    case TVN_BEGINDRAG:
        {
            // Notifies the tree view control's parent window that a drag
            // and drop operation is being initiated.

            return 0;
        }

        case TVN_BEGINLABELEDIT:
        {
            // Notifies the tree view control's parent window about the start
            // of label editing for an item.

            return 0;
        }

    case TVN_ITEMEXPANDED:
    {
      // Notifies a tree view control's parent window that a parent
      // item's list of child items has expanded or collapsed. This
      // notification message is sent in the form of a WM_NOTIFY
      // message.

      return 0;
    }

    case TVN_ITEMEXPANDING:
    {
      // Notifies a tree view control's parent window that a parent
      // item's list of child items is about to expand or collapse.

      return 0;
    }

    default:
      return 0;
  }
  break;
}


Creating an Up-Down Control

An up-down control is a pair of arrow buttons a user can tap with the stylus to increment or decrement a value. An up-down control may be used in one of two ways: as a stand-alone control or in conjunction with another control, called a buddy window. In Windows CE-based applications, up-down controls can only be “buddies” with edit controls. When an up-down control is paired with an edit control, it is called a spin control.

Up-down control and buddy window

You create an up-down control by using the CreateUpDownControl function. This class is registered when the common control DLL is loaded. You can use the InitCommonControls function to ensure that this DLL is loaded.

To register the up-down control class using the InitCommonControlsEx function, specify the ICC_UPDOWN_CLASS flag as the dwICC member of the INITCOMMONCONTROLSEX structure you pass in the lpInitCtrls parameter.

The following code example is the syntax for the CreateUpDownControl function.

HWND CreateUpDownControl (DWORD dwStyle,
                          int x,
                          int y,
                          int cx,
                          int cy,
                          HWND hParent,
                          int nID,
                          HINSTANCE hInst,
                          HWND hBuddy,
                          int nUpper,
                          int nLower,
                          int nPos);

Here, dwStyle specifies the style of the up-down control. You must include the WS_CHILD, WS_BORDER, and WS_VISIBLE styles, and it may include any of the window styles specific to the up-down control. For a complete listing of supported styles, see Window and Control Styles

The control’s upper left corner is positioned at x, y, and its dimension is determined by cx and cy. The handle to the parent window is passed in hParent and the control identifier is specified in nID. The handle to the application is passed in hInst. The handle to the buddy window is passed in hBuddy. If there is no buddy window, this parameter must be NULL.

The upper-limit range of the control is passes in nUpper and the lower-limit range is passed in nLower. The initial position of the control is specified in nPos. This value must be within the specified range.


Modifying Control Position and Acceleration

After you have created an up-down control, you can change it in several ways. You can change its current position, minimum position, and maximum position by sending messages. You can change the radix base, that is, either base 10 or base 16, used to display the current position in the buddy window. You can change the rate at which the current position changes when the up or down arrow is tapped.

To retrieve the current position of an up-down control, use the UDM_GETPOS message. For an up-down control with a buddy window, the current position is the number in the buddy window’s caption. The up-down control retrieves the current caption and updates its current position if the caption has changed because the user edited the text of an edit control.

The buddy window’s caption can be either a decimal or hexadecimal string, depending on the radix base of the up-down control. Set the radix base by using the UDM_SETBASE message and retrieve the radix base by using the UDM_GETBASE message.

The UDM_SETPOS message sets the current position of a buddy window. Note that unlike a scroll bar, an up-down control automatically changes its current position when the up and down arrows are tapped. Therefore, an application does not need to set the current position when processing the WM_VSCROLL or WM_HSCROLL message.

You can change the minimum and maximum positions of an up-down control by using the UDM_SETRANGE message. The maximum position may be less than the minimum, in which case tapping the up arrow button decreases the current position. Put another way, up moves toward the maximum position. To retrieve the minimum and maximum positions for an up-down control, use the UDM_GETRANGE message.

You can control the rate at which the position changes when the user holds down an arrow button by setting the up-down control’s acceleration. The acceleration is defined by an array of UDACCEL structures. Each structure specifies a time interval and the number of units by which to increment or decrement at the end of that interval. To set the acceleration, use the UDM_SETACCEL message. To retrieve acceleration data, use the UDM_GETACCEL message.


Creating a Date and Time Picker Control

The date and time picker (DTP) is a control that displays information about dates and times, and provides users with an easy way to modify this information. Each field in the control displays a time element, such as month, day, hour, or minute. A user selects a field by tapping it with the stylus and then types a new value from the keyboard.

The way date and time information is displayed is determined by a format string. A DTP control can display time information in any of three preset formats, or you can create custom format strings to specify a different order in which to display the fields. You can also add customized date and time information to a DTP control by using callback fields.

To create a date and time picker control

  1. Specify DATETIMEPICK_CLASS in the lpClassName parameter of the CreateWindowEx function.

    This class is registered when the common control DLL is loaded. You can use the InitCommonControls function to ensure that this DLL is loaded. To register the date and time picker class using the InitCommonControlsEx function, specify the ICC_DATE_CLASSES flag as the dwICC member of the INITCOMMONCONTROLSEX structure you pass in the lpInitCtrls parameter.

  2. Specify a DTP style in the dwStyle parameter of the CreateWindowEx function.

    For a complete listing of supported styles, see Window and Control Styles

Windows CE date and time picker


Displaying Information

As stated earlier, a DTP control relies on a format string to determine how it will display fields of information. By default, a DTP control can display time information fields in three preset formats or according to a custom format string. Custom format strings provide flexibility for your application. In a custom format string, you can specify the order in which the control will display fields of information or indicate specific callback fields. The format characters of the format string define the DTP control’s display and field layout.

The following list shows Window styles used by the preset formats, which are format strings.

DTS_LONGDATEFORMAT
The control displays the date in long format. The default format string for this style is defined by LOCALE_SLONGDATEFORMAT, which produces the output “Friday, April 19, 1998.”
DTS_SHORTDATEFORMAT
The control displays the date in short format, which is the default style setting. The default format string for this style is defined by LOCALE_SSHORTDATE, which produces the output “4/19/98.”
DTS_TIMEFORMAT
The control displays the time. The default format string for this style is defined by LOCALE_STIMEFORMAT, which produces the output “5:31:42 PM.” An up-down control is placed to the right of the DTP control to modify time values.

You can customize the display of a DTP control using custom format strings. DTP controls support specified format characters that you can combine to create a format string. To assign the format string to the DTP control, use the DTM_SETFORMAT message.

The following table shows format characters supported by DTP controls.

String fragment
Description
d | The one-digit or two-digit day
dd | The two-digit day. Single-digit day values are preceded by a zero.
ddd | The three-character weekday abbreviation
dddd | The full weekday name
gg | The period and era string contained in the CAL_SERASTRING value associated with the specified locale. Windows CE ignores this element if the date to be formatted does not have an associated era or period string.
h | The one-digit or two-digit hour in 12-hour format
hh | The two-digit hour in 12-hour format. Single-digit values are preceded by a zero.
H | The one-digit or two-digit hour in 24-hour format
HH | The two-digit hour in 24-hour format. Single-digit values are preceded by a zero.
m | The one-digit or two-digit minute
mm | The two-digit minute. Single-digit values are preceded by a zero.
M | The one-digit or two-digit month number
MM | The two-digit month number. Single-digit values are preceded by a zero.
MMM | The three-character month abbreviation
MMMM | The full month name
t | The one-letter A.M. and P.M. abbreviation (that is, “AM” is displayed as “A”)
tt | The two-letter A.M. and P.M. abbreviation (that is, “AM” is displayed as “AM”)
X | The callback field. The control uses the other valid format characters and queries the application to fill in the “X” portion of the string. The application must be prepared to handle the DTN_WMKEYDOWN, DTN_FORMAT, and DTN_FORMATQUERY notification messages. Multiple “X” characters can be used in a series to signify unique callback fields.
y | The year is displayed as the last two digits, but with no leading zero for years less than 10.
yy | The last two digits of the year. For example, 1998 would be displayed as “98.”
yyy | The full year. For example, 1998 would be displayed as “1998.”

You can add body text to the format string. For example, if you want the control to display the current date with the format “Today is: 04:22:31 Tuesday Mar 23, 1998”, use the following format string: Today is: 'hh':'m':'s ddddMMMdd', 'yyy' The window procedure for a command bar automatically sets the size of the command bar and positions it along the top of the parent window client area. It also destroys the command bar when its parent window is destroyed. Body text must be enclosed in single quotation marks.

Note that segments of nonformat characters in the preceding example are delimited by single quotation marks. Failure to surround body text in this way will result in unpredictable display by the DTP control.


Customizing Output with Callback Fields

In addition to the standard format characters that define DTP fields, you can customize your output by specifying certain parts of a format string as callback fields. To declare a callback field, include one or more ASCII Code 88 “X” characters anywhere in the body of the format string. Like other DTP control fields, callback fields are displayed in left-to-right order, based on their location in the format string.

You can create unique callback fields by repeating the “X” character. Thus, the following format string contains two callback fields: 'XXddddMMMdd', 'yyyXXX'. Remember, because callback fields are treated as valid fields, your application must be prepared to handle DTN_WMKEYDOWN notification messages.

When the DTP control parses the format string and encounters a callback field, it sends DTN_FORMAT and DTN_FORMATQUERY notification messages. The owner of the control must respond to these notifications to ensure the custom information is properly displayed.


Creating a Month Calendar Control

A month calendar control is a child window that displays a monthly calendar. The calendar can display one or more months at a time.

Month calendar control

When a user taps the name of a month with the stylus, a pop-up menu appears that lists all the months of the year. A user can select a month by tapping its name on the menu. A user who is using the DTP control can use ALT+DOWN ARROW to activate the month calendar control. The user can scroll the displayed months forward or backward either by tapping the left arrow or right arrow at the top of the control or by pressing the PAGE UP or PAGE DOWN keys on the keyboard. When a user taps the year displayed at the top of the calendar next to the month, an up-down control appears. The user can use this control to change the year. The user can also use CTRL+PAGE UP or CTRL+PAGE DOWN to scroll from one year to another. A user can press keys on the keyboard to navigate; the arrow keys scroll between days, the HOME key moves to the beginning of a month, and the END key moves to the end of a month. Unless the calendar has the MCS_NOTODAY style, the user can return to the current day by tapping the Today label at the bottom of the month calendar control.

To create a month calendar control

  1. Specify MONTHCAL_CLASS in the lpClassName parameter of the CreateWindowEx function.

    This class is registered when the common control DLL is loaded. You can use the InitCommonControls function to ensure that this DLL is loaded. To register the date and time picker class using the InitCommonControlsEx function, specify the ICC_DATE_CLASSES flag as the dwICC member of the INITCOMMONCONTROLSEX structure you pass in the lpInitCtrls parameter.

  2. Specify a DTP style in the dwStyle parameter of the CreateWindowEx function.

    A month calendar control that uses the MCS_DAYSTATE style supports day states. The control uses day state data to determine how it draws specific days within the control. Day state data is expressed as a 32-bit data type, MONTHDAYSTATE. Each bit in a MONTHDAYSTATE bit field, from 1 through 31, represents the state of a day in a month. If a bit is on, the corresponding day will be displayed in bold; otherwise it will be displayed with no emphasis. An application can explicitly set day state data by sending the MCM_SETDAYSTATE message or by using the corresponding macro, MonthCal_SetDayState. Additionally, month calendar controls that use the MCS_DAYSTATE style send MCN_GETDAYSTATE notification messages to request day state data. For a complete listing of supported styles, see Window and Control Styles


Setting the Time

Because the month calendar control is created, it will insert the current time into its “today” date and time. Later, when a time is set programmatically, the control will either copy the time fields as they are or validate them first, and then, if invalid, store the current default time. The following table shows messages that set a date, and the manner in which those messages affect time fields.

Message
Description
MCM_SETCURSEL | The control copies the time fields as they are, without validation or modification.
MCM_SETRANGE | The control validates the time fields of the structures passed in. If valid, the time fields are copied without modification. If invalid, the control copies the time fields from the “today” date and time.
MCM_SETSELRANGE | The control validates the time fields of the structures passed in. If valid, the time fields are copied without modification. If invalid, the control retains the time fields from the current selection ranges.
MCM_SETTODAY | The control copies the time fields as they are, without validation or modification.

When a date is retrieved from the month calendar control, the time fields will be copied from the stored times without modification. Handling of the time fields by the control is provided as a convenience. The control does not examine or modify the time fields as a result of any operation other than those previously listed.


Creating a Status Bar

A status bar, also known as a status window, is a horizontal window positioned at the bottom of a parent window. It displays status information defined by the application.

Status bar

You create a status bar by calling the CreateStatusWindow function. This class is registered when the common control DLL is loaded. You can use the InitCommonControls function to ensure that this DLL is loaded. To register the status bar class using the InitCommonControlsEx function, specify the ICC_ BAR_CLASSES flag as the dwICC member of the INITCOMMONCONTROLSEX structure you pass in the lpInitCtrls parameter.

Because status bars are windows, you can create a status bar by calling CreateWindow or CreateWindowEx, and specifying the window class STATUSCLASSNAME.

The window procedure for the status bar control automatically sets the initial size and position of the window. The width is the same as that of the parent window’s client area. The height is based on the width of the window’s borders and on the metrics of the font currently selected into the status bar’s device context.

The window procedure automatically adjusts the size of the status bar when it receives a WM_SIZE message. Typically, when the size of the parent window changes, the parent sends a WM_SIZE message to the status bar.

An application can set the minimum height of a status bar drawing area by sending the window an SB_SETMINHEIGHT message that specifies the minimum height in pixels. The drawing area does not include the window borders.

You retrieve the widths of the borders of a status bar by sending the window an SB_GETBORDERS message. The message includes the address of a three-element array that receives the widths.


Creating a Multiple-Part Status Bar

A status bar can have many different parts, each displaying a different line of text. You divide a status bar into parts by sending the window an SB_SETPARTS message, which specifies the number of parts to create and the address of an integer array. The array contains one element for each part, and each element specifies the client coordinate of the right edge of a part.

A status bar can have a maximum of 255 parts, although applications typically use fewer. You retrieve a count of the parts in a status bar, as well as the coordinate of the right edge of each part, by sending the window an SB_GETPARTS message.

A simple mode status bar is useful for displaying Help text for menu items while the user is scrolling through the menu. You put a status bar in simple mode by sending it an SB_SIMPLE message. A simple mode status bar displays only one part. When the text of the window is set, the window is invalidated, but not redrawn until the next WM_PAINT message. Waiting for the message reduces screen flicker by minimizing the number of times the window is redrawn.

The string that a status bar displays while in simple mode is maintained separately from the strings that it displays while it is not in simple mode. This means you
can put the window in simple mode, set its text, and switch out of simple mode without the original text being changed.

Windows CE supports a status bar notification, SBN_SIMPLEMODECHANGE, that a status bar sends when the simple mode changes as a result of receiving an SB_SIMPLE message.


Adding Status Bar Text

You set the text of any part of a status bar by sending the SB_SETTEXT message, specifying the zero-based index of a part, an address of the string to draw in the part, and the technique for drawing the string. The drawing technique determines whether the text has a border and, if it does, the style of the border. It also determines whether the parent window is responsible for drawing the text.

By default, text is left-aligned within the specified part of a status bar. You can embed tab characters, for example, \ t, in the text to center it or right-align it. Text to the right of a single tab character is centered, and text to the right of a second tab character is right-aligned.

To retrieve text from a status bar, use the SB_GETTEXTLENGTH and SB_GETTEXT messages.

If your application uses a status bar that has only one part, you can perform text operations by using the WM_SETTEXT, WM_GETTEXT, and WM_GETTEXTLENGTH messages. These messages deal only with the part that has an index of zero, enabling you to treat the status bar much like a static text control.

To display a line of status information without creating a status bar, use the DrawStatusText function. The function uses the same techniques to draw the status information as it uses to draw the window procedure for the status bar, but it does not automatically set the size and position of the status information. When calling the DrawStatusText function, you must specify the size and position of the status information as well as the device context of the window in which to draw it.


Creating a Progress Bar

A progress bar is a common control that indicates the progress of a lengthy operation by displaying a colored bar inside a horizontal rectangle. The length of the bar in relation to the length of the rectangle corresponds to the percentage of the operation that is complete.

Progress bar

To create a progress bar

  1. Specify PROGRESS_CLASS in the lpClassName parameter of the CreateWindowEx function.

    This class is registered when the common control DLL is loaded. You can use the InitCommonControls function to ensure that this DLL is loaded. To register the progress bar class using the InitCommonControlsEx function, specify the ICC_PROGRESS_CLASS flag as the dwICC member of the INITCOMMONCONTROLSEX structure you pass in the lpInitCtrls parameter.

  2. Specify a progress bar style in the dwStyle parameter of the CreateWindowEx function.


Setting the Range and Current Position

A progress bar’s range represents the entire duration of the operation, and the current position represents the progress that the application has made toward completing the operation. The window procedure uses the range and the current position to determine the percentage of the progress bar to fill with the highlight color as well as to determine what text, if any, to display within the progress bar.

If you do not set the range values, the system sets the minimum value to zero and the maximum value to 100. You can adjust the range to convenient integers by using the PBM_SETRANGE message.

A progress bar provides several messages that you can use to set the current position. The PBM_SETPOS message sets the position to a specified value. The PBM_DELTAPOS message advances the position by adding a specified value to the current position. The PBM_SETSTEP message enables you to specify a step increment for a progress bar. Subsequently, when you send the PBM_STEPIT message to the progress bar, the current position advances by the specified increment. The default step increment is 10.

Note The range values in a progress bar are considered signed integers. Any number greater than 0x7FFFFFFF is interpreted as a negative number.


Creating a Property Sheet

A property sheet is a system-defined dialog box that you use to view or modify object attributes or properties. A property sheet includes a frame, a title bar, and three buttons: OK, Cancel (X), and Help (?), located atop the window. To use property sheets, you must include the Prsht.h header file in your application.

A property sheet contains and manages one or more related dialog boxes, called property pages. Each page in a property sheet is an application-defined modeless dialog box that manages the controls that enable a user to view and edit the properties of an object. A property sheet must contain at least one property page, but cannot contain more than the value of MAXPROPPAGES as defined in the header files.

Users access property sheets by using an ALT+Tap action. Users access property sheets by using an ALT+Tap action.

Users access property sheets by using an ALT+Tap action. A property sheet sends a notification message to the dialog box procedure for a page when the page becomes active or inactive and when the user taps the OK, Cancel (X), or Help (?) button. The notifications are sent in the form of WM_NOTIFY messages. The lParam parameter of the WM_NOTIFY messages points to an NMHDR structure, which includes the window handle of the property sheet dialog box. Some notification messages require that a property sheet page return either TRUE or FALSE in response to the WM_NOTIFY message. To respond, the page must use the SetWindowLong function to set the DWL_MSGRESULT value for the page dialog box to either TRUE or FALSE.

Each page has a corresponding label, which the property sheet displays in the tab that it creates for the page. Because all property sheet pages expect you to use a Roman font, not bold, you must ensure that the font is not bold by specifying the DS_3DLOOK style in the dialog box template.

Windows CE property sheet

Before creating a property sheet, you must define one or more pages.

To define a property sheet page

  1. Create a PROPSHEETPAGE structure that contains data about a property sheet icon, label, dialog box template, dialog box procedure, and other attributes.
  2. Call the CreatePropertySheet function and pass it a pointer to the PROPSHEETPAGE structure. The function returns a HPROPSHEETPAGE handle to the property page.

Once you have defined one or more property sheet pages, you can create a property sheet. One way to create a property sheet is to specify the address of a PROPSHEETHEADER structure in a call to the PropertySheet function. The structure defines the icon and title for the property sheet and also includes a pointer to an array of HPROPSHEETPAGE handles. When PropertySheet creates the property sheet, it includes the pages identified in the array. The order of the array determines the order of the pages in the property sheet.

Another method to create a property sheet is to specify an array of PROPSHEETHEADER structures instead of an array of HPROPSHEETPAGE handles. In this case, PropertySheet creates handles for the pages before adding them to the property sheet.

The PropertySheet function automatically sets the size and initial position of a property sheet. The position is based on the position of the owner window, and the size is based on the largest page specified in the array of pages when the property sheet is created.

After creating a property sheet, you can add and remove pages by using the PSM_ADDPAGE message. Note that the size of the property sheet cannot change after it has been created, so the new page must be no larger than the largest page currently in the property sheet. To remove a page, use the PSM_REMOVEPAGE message. When you define a page, you can specify the address of the PropSheetPageProc callback function that the property sheet calls when it creates or removes the page. Using PropSheetPageProc enables you to initialize and cleanup individual property sheet pages.

To destroy a page that was created by the CreatePropertySheetPage function, but was not added to the property sheet, use the DestroyPropertySheetPage function. Destroying a property sheet automatically destroys all pages that have been added. The system destroys the pages in reverse order from that specified in the array used to create the pages.

You specify the title of a property sheet in the PROPSHEETHEADER structure that you used to create the property sheet. If the dwFlags member includes the PSH_PROPTITLE value, the property sheet adds the prefix “Properties” to the specified title string. Use the PSM_SETTITLE message to change the title after a property sheet has been created.

By default, a property sheet uses the name string specified in the dialog box template as the label for the property page sheet page. You can override the name string by including the PSP_USETITLE value as the dwFlags member of the PROPSHEETPAGE structure that defines the page. When PSP_USETITLE is specified, the pszTitle member must contain the address of the label string for the page.


Working with Active and Inactive Property Sheet Pages

A property sheet can have only one active page at a time. The active sheet is at the top of the overlapping stack of pages. The user activates a page by selecting its tab; an application uses the PSM_SETCURSEL message to activate a page. Before the subsequent active page is visible, the property sheet sends it the PSN_SETACTIVE notification message. The page should respond by initializing its control windows.

The property sheet determines whether to enable or disable the Help button for an active page by checking for the PSP_HASHELP style. If the page has this style, it supports the Help button. If the PSP_HASHELP style is not present, it disables the button. When the user taps the Help button, the active page receives the PSN_HELP notification message. The page should respond by displaying help information.

When the user taps OK, the property sheet sends the PSN_KILLACTIVE notification message to the active page, giving it an opportunity to validate the user’s changes. If the page determines that the changes are valid, it should call the SetWindowLong function to set the DWL_MSGRESULT value for the page to FALSE. In this case, the property sheet sends the PSN_APPLY notification message to each page, directing it to apply the new properties to the corresponding item. If the page determines that the user’s changes are not valid, it should set DWL_MSGRESULT to TRUE and display a dialog box informing the user of the problem. The page remains active until it sets DWL_MSGRESULT to FALSE in response to a PSN_KILLACTIVE message.

The property sheet sends the PSN_RESET notification message to all pages when the user taps the Cancel button, indicating that it is about to destroy the property sheet. A page should use the notification to perform cleanup operations.

Note To set the position of a property sheet window in an application, use the SetWindowPos function rather than the MoveWindow function. Call SetWindowPos in the dialog box procedure of the property page that will open first when the user activates a property sheet.


Creating a Tab Control

A tab control is analogous to a set of dividers in a notebook or labels in a file cabinet. In a property sheet, a user selects a tab to move from one property sheet page to another.

Windows CE tab control

You send messages to a tab control to add tabs and otherwise affect the control’s appearance and behavior. Each message has a corresponding macro, which you can use instead of sending the message explicitly. Though you cannot disable an individual tab in a tab control, you can disable a tab control in a property sheet by disabling the corresponding page.

Each tab in a tab control consists of a label and application-defined data. This data is specified by a TCITEM structure. You can add tabs to a tab control, get the number of tabs, retrieve and set the contents of a tab, and delete tabs. Tabs are identified by their zero-based index.

Windows CE supports two extended tab control styles. The first style uses the TCM_SETEXTENDEDSTYLE message or its corresponding macro, TabCtrl_SetExtendedStyle, to set the extended style. It uses the TCM_GETEXTENDEDSTYLE message or its corresponding macro, TabCtrl_GetExtendedStyle, to retrieve the extended style.

Note Because an extended tab control style is not the same as an extended window style, you cannot pass an extended tab control style to CreateWindowEx when you create a tab control.

The second extended tab control style, TCS_EX_FLATSEPARATORS, draws a separator between tab items in tab controls that have the TCS_BUTTONS or TCS_FLATBUTTONS style. When you create a tab control with the TCS_FLATBUTTONS style, this extended style is set by default.

To create a tab control

  1. Specify the WC_TABCONTROL class in the lpClassName parameter of the CreateWindowEx function.

    Windows CE registers this class when it loads the common control DLL. You can use the InitCommonControls function to ensure that this DLL is loaded. To register the tab control class using the InitCommonControlsEx function, specify the ICC_ TAB_CLASSES flag as the dwICC member of the INITCOMMONCONTROLSEX structure you pass in the lpInitCtrls parameter.

  2. Specify a tab control style in the dwStyle parameter of the CreateWindowEx function.

You can add tabs to the control using the TCM_INSERTITEM message, which specifies the position of the tab and the address of its TCITEM structure. You can retrieve and set the contents of an existing tab by using the TCM_GETITEM and TCM_SETITEM messages. For each tab, you can specify an icon, a label, or both. You can also specify application-defined data to associate with the tab.

You can associate application-defined data with each tab. For example, you might save information about each page with its corresponding tab. By default, a tab control allocates four extra bytes per tab for application-defined data. You can change the number of extra bytes per tab by using the TCM_SETITEMEXTRA message. You can only use this message when the tab control is empty.

The lParam member of the TCITEM structure specifies application-defined data. If you use more than four bytes of application-defined data, you need to define your own structure and use it instead of TCITEM. You can retrieve and set application-defined data the same way you retrieve and set other information about a tab: Use the TCM_GETITEM and TCM_SETITEM messages.

Note Windows CE does not support vertical text. If you create vertical tabs, and want to use vertical text, you have to create a text bitmap and rotate it. Then, you can add the bitmap to an image list and attach it to the tab by specifying its image list index in the iImage member of the TCITEM or TCITEMHEADER structure.


Handling Tab Control Messages

When the user selects a tab, a tab control sends notification messages to its parent window in the form of WM_NOTIFY messages. The tab control sends the TCN_SELCHANGING notification message before the selection changes, and it sends the TCN_SELCHANGE notification message after the selection changes.

You can process TCN_SELCHANGING to save the state of the outgoing page. You can return TRUE to prevent the selection from changing. For example, you might not want to switch away from a child dialog box in which a control has an invalid setting.

To display the incoming page in the display area, you must process TCN_SELCHANGE. Though processing might include changing the information displayed in a child window, it will more likely entail destroying or hiding the outgoing child window or dialog box and creating or showing the incoming child window or dialog box.

You can retrieve and set the current table selection by using the TCM_GETCURSEL and TCM_SETCURSEL messages.


Adding a Tab Control Image List

Each tab can have an icon associated with it. An index specifies the icon into the image list for the tab control. When you create a tab control, it has no image list associated with it. You can use the ImageList_Create function to create an image list. You can assign it to a tab control by using the TCM_SETIMAGELIST message.

You can add an image to a tab control’s image list just as you would add one to any other image list. To ensure that each tab remains associated with its assigned image, remove images by using the TCM_REMOVEIMAGE message instead of the ImageList_Remove function.

Because destroying a tab control does not destroy an associated image list, you must destroy the image list separately. Retaining the image list may be useful if you want to assign the same image list to multiple tab controls.

To retrieve the handle of the image list currently associated with a tab control, you can use the TCM_GETIMAGELIST message.


Setting Tab Size and Position

The tab control display area is the area of the control in which an application displays the current page. An application creates a child window or dialog box to display the current page, and then it sets the window size and position to fit the display area. You can use the TCM_ADJUSTRECT message to calculate a tab control’s display area based on the dimensions of a specified rectangle, or to calculate the dimensions of rectangle given the coordinates of a display area.

Each tab in a tab control has a size and a position. You can set the size of tabs, retrieve the bounding rectangle of a tab, or determine which tab is located at a specified position.

For fixed-width and owner-drawn tab controls, you can set the exact width and height of tabs by using the TCM_SETITEMSIZE message. In other tab controls, you calculate each tab’s size based on the icon and label for the tab. The tab control includes space for a border and an additional margin. You can set the thickness of the margin by using the TCM_SETPADDING message.

You use messages and styles to learn about tabs. You can determine the current bounding rectangle for a tab by using the TCM_GETITEMRECT message. You can determine which tab, if any, is at a specified location by using the TCM_HITTEST message. In a tab control with the TCS_MULTILINE style, you can determine the current number of rows of tabs by using the TCM_GETROWCOUNT message.


Using the Custom Draw Service

Windows CE supports the custom draw service. The custom draw service is not a common control; it is a service that makes it easy to customize a common control’s appearance. You can use it to change a common control’s color or font, or to partially or completely draw the control.

A common control that supports the custom draw service provides this service by sending an NM_CUSTOMDRAW notification at specific times during drawing operations. The lParam of the NM_CUSTOMDRAW notification is a reference to an NMCUSTOMDRAW structure. If the control is a list view, it uses the NMLVCUSTOMDRAW structure; if the control is a tree view, it uses the NMTVCUSTOMDRAW structure. This structure contains data that the application can use to determine how to draw the control. The following common controls can provide the custom draw service:

Command bands
Header controls
List views
Toolbars
Trackbars
Tree views


Handling Paint Cycles, Drawing Stages, and Notification Messages

Common controls paint and erase themselves based on messages received from the OS or other applications. The process of a control painting or erasing itself is called a paint cycle. Controls that support custom draw send NM_CUSTOMDRAW notification messages periodically throughout each paint cycle. This notification message is accompanied by an NMCUSTOMDRAW structure or another structure that contains an NMCUSTOMDRAW structure as its first member.

In addition to other data, the NMCUSTOMDRAW structure informs the parent window about what stage of the paint cycle the control is in. This is referred to as the draw stage, and is represented by the value in the structure’s dwDrawStage member. A control informs its parent about four basic, or global, draw stages. The following table shows the flag values, defined in Commctrl.h, that represent these stages in the structure.

Global draw stage value
Description
CDDS_PREPAINT | Before the paint cycle begins
CDDS_POSTPAINT | After the paint cycle is complete
CDDS_PREERASE | Before the erase cycle begins
CDDS_POSTERASE | After the erase cycle is complete

Each of the preceding values can be combined with the CDDS_ITEM flag to specify draw stages for items. The following table shows item-specific values contained in Commctrl.h.

Item-specific draw stage value
Description
CDDS_ITEMPREPAINT | Before an item is drawn
CDDS_ITEMPOSTPAINT | After an item has been drawn
CDDS_ITEMPREERASE | Before an item is erased
CDDS_ITEMPOSTERASE | After an item has been erased

You must process the NM_CUSTOMDRAW notification message and then return a specific value that informs the control what it must do.

The key to harnessing custom draw features is in responding to the NM_CUSTOMDRAW notification messages that a control sends. The return values your application sends in response to these notifications determine the control behavior for that paint cycle.


Responding to the Prepaint Notification

At the beginning of each paint cycle, the control sends the NM_CUSTOMDRAW notification message, which specifies the CDDS_PREPAINT value in the dwDrawStage member of the accompanying NMCUSTOMDRAW structure. The value that your application returns to this first notification dictates how and when the control sends subsequent custom draw notifications for the rest of that paint cycle. The following table shows the flags that your application can return in response to the first notification.

Return value
Effect
CDRF_DODEFAULT | The control draws itself. It does not send additional NM_CUSTOMDRAW messages for this paint cycle. This flag cannot be used with any other flag.
CDRF_NOTIFYITEDRAW | The control notifies the parent of any item-specific drawing operations. It sends NM_CUSTOMDRAW notification messages before and after it draws items.

If your application returns CDRF_NOTIFYITEMDRAW to the initial prepaint custom draw notification, the control sends notifications for each item it draws during that paint cycle. These item-specific notifications have the CDDS_ITEMPREPAINT value in the dwDrawStage member of the accompanying NMCUSTOMDRAW structure. Your application can request that the control send another notification when it is done drawing the item by returning CDRF_NOTIFYPOSTPAINT to these item-specific notifications. Otherwise, your application can return CDRF_DODEFAULT and the control will not notify the parent window until it starts to draw the next item.

If your application draws the item, it should return CDRF_SKIPDEFAULT. This enables the control to skip items that it need not draw, which conserves resources. Remember that returning this value means that the control will not draw any portion of the item, so your application must draw any item images.


Changing Fonts and Colors

Your application can use the custom draw service to change an item font. To do this, select the HFONT you want into the device context specified by the hdc member of the NMCUSTOMDRAW structure associated with that notification. Because the font you select might have different metrics from the default font, be sure that you include the CDRF_NEWFONT bit in the return value for the notification message. For more information, see the code examples in the “Sample Custom Draw Function” later in this chapter.

The font that your application specifies is used to display that item when it is not selected. custom draw does not enable you to change the font attributes for selected items.


Sample Custom Draw Function

Upon receiving the prepaint notification CDDS_PREPAINT, the function requests item-specific notifications by returning CDRF_NOTIFYITEMDRAW. When it receives the subsequent item-specific notifications, it selects a previously created font into the provided device context and specifies new colors before returning CDRF_NEWFONT. The following code example shows how an application-defined function processes custom draw notification messages sent by a child list view control.

LRESULT DoNotify (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  LPNMLISTVIEW pnm = (LPNMLISTVIEW)lParam;

  switch (pnm->hdr.code)
  {
    case NM_CUSTOMDRAW:
    {
      LPNMLVCUSTOMDRAW  lplvcd = (LPNMLVCUSTOMDRAW)lParam;

      if (lplvcd->nmcd.dwDrawStage == CDDS_PREPAINT)
        return CDRF_NOTIFYITEMDRAW;

      if (lplvcd->nmcd.dwDrawStage == CDDS_ITEMPREPAINT)
      {
        if (!(lplvcd->nmcd.dwItemSpec % 3))
          SelectObject (lplvcd->nmcd.hdc, g_hNewFont);
        else
          return CDRF_DODEFAULT;

        lplvcd->clrText = RGB(150, 75, 150);
        lplvcd->clrTextBk = RGB(255,255,255);

        return CDRF_NEWFONT;
      }
    }

    default:
      break;
  }

  return 0;
}


Working with Graphics

In Microsoft Windows CE, the graphics device interface (GDI) controls the display of text and graphics. GDI provides several functions and structures you can use to generate graphic output for displays, printers, and other devices. Using GDI functions, you can draw lines, curves, closed figures, text, and bitmapped images. The color and style of the items you draw depends on the drawing objects you create. GDI provides three drawing objects you can use to create graphics: pens, to draw lines and curves, brushes, to fill the interiors of closed figures, and fonts, to write text.

The Windows CE GDI is designed for devices with limited system resources. Therefore, it does not include many of the special graphic functions found in Windows-based desktop platforms. As a consequence, the Windows CE GDI is a powerful, full-color graphic display system with a small footprint.

Applications direct output to a given device by creating a device context for the device. The device context is a GDI-managed structure containing information about the device. An application creates a device context by calling device context functions. GDI returns a device context handle used to identify the device.

Applications can direct output to a physical device, such as a display or printer, or to a logical device, such as a memory device.

A device context also contains attributes that determine how GDI functions interact with a device. These attributes eliminate the need to specify every piece of information Windows CE requires to display an object on a device. If you want to change an attribute, you can use attribute functions to change current device settings and operating modes. Operating modes include text and background colors, and the mixing mode that specifies how colors in a pen or brush combine with colors already on a display surface.


Getting a Handle to a Device Context

Windows CE provides several methods for obtaining a device context handle, depending on whether the device is a display, printer, or memory device. You use a display device context to draw in the client area of a screen. You use a memory device context to store bitmapped images in memory rather than sending it to an output device. A memory device context enables Windows CE to treat a portion of memory as a virtual device. You use a printer device context to send output to a printer.

It is important when using a device context that you release or delete it when it is no longer in use. Releasing a device context frees the device for use by other applications.

Note Failure to delete objects no longer in use can significantly affect performance.


Obtaining a Display Device Context

To get a handle to a display device context, call the BeginPaint or GetDC function and supply a handle to a window. Windows CE returns a handle to a display device context with default objects, attributes, and graphic modes. Newly created device contexts start with default brush, palette, font, pen, and region objects. You can begin drawing using these defaults, or you can choose a new object, change the attributes of an existing object, or choose a new mode.

You can examine a default object’s attributes by calling the GetCurrentObject and GetObject functions. The GetCurrentObject function returns a handle identifying the current pen, brush, palette, bitmap, or font, and the GetObject function initializes a structure containing the object attributes.

The following table shows the object-specific creation functions you can call to replace a default object.

Graphic object
Creation functions
Bitmap | CreateBitmap, CreateCompatibleBitmap, CreateDIBSection
Brush | CreateDIBPatternBrushPt, CreatePatternBrush, CreateSolidBrush
Palette | CreatePalette
Font | CreateFontIndirect
Pen | CreatePen, CreatePenIndirect

Each of these functions returns a handle identifying the new object. After you retrieve a handle, you can call the SelectObject function to select the new object into the device contex. However, you should save the SelectObject return value because it is the handle to the default object. When you finish using the new object, use SelectObject to restore the default object, and delete the new object with the DeleteObject function.

When you have finished drawing in the display area, you must release the device context by calling the EndPaint or ReleaseDC function. If you called BeginPaint to create the device context, then call EndPaint to release it. If you called GetDC to create the device context, then call ReleaseDC to release it. Generally, you call BeginPaint and EndPaint while processing WM_PAINT messages in your window procedure. Otherwise, call GetDC and ReleaseDC to obtain and release a device context.

The following code example shows how to call GetDC and ReleaseDC to obtain and release a device context and how to call SelectObject to get a new object.

HDC hDC;                // Handle to a display device context
HBRUSH hBrush,          // Handle to the new brush object
       hOldBrush;       // Handle to the old brush object

// Retrieve the handle to the display device context.
if (!(hDC = GetDC (hwnd)))
  return;

// Create a solid brush and select it into the device context.
hBrush = CreateSolidBrush (RGB(0, 255, 255));
hOldBrush = SelectObject (hDC, hBrush);

// Draw a rectangle.
Rectangle (hDC, 0, 0, 100, 200);

// Select the old brush back into the device context.
SelectObject (hDC, hOldBrush);

// Delete the new brush object.
DeleteObject (hBrush);

// Release the device context.
ReleaseDC (hwnd, hDC);


Obtaining a Memory and Printer Device Context

You can create a memory device context for a device by calling the CreateCompatibleDC function and supplying a handle to the device context. Memory device contexts are also called compatible device contexts because they are created to be compatible with a particular device. Windows CE creates a temporary 1 x 1 pixel monochrome bitmap and selects it into the device context after calling CreateCompatibleDC. Before you can draw with this device context, you must call the SelectObject function to select a bitmap with the appropriate width, height, and color depth into the device context. Once the new bitmap is selected into the memory device context, you can use the device context to store images. For more information on image storage, see “Creating Bitmaps” later in this chapter.

You obtain a handle to a printer device context by calling the CreateDC function. Call the DeleteDC function to delete the printer device context when finished printing.

Note You must delete, rather than release, a printer or memory device context; the ReleaseDC function fails if you try to use it to release a printer or memory device context.


Modifying a Device Context

Once you have created a device context, call GetDeviceCaps to retrieve device data. GetDeviceCaps provides data about a device’s color format and raster capabilities, as well as its shape, text, and line drawing capabilities.

Before modifying a device context, save the current device context settings. Call the SaveDC function to record the condition of your device context’s graphic objects and graphic modes to a special GDI stack. Call this function to save your application’s original state. Call RestoreDC to return the device context to this original state.

To modify the appearance of a device context, you can use graphics mode functions. Graphics modes control general display characteristics, such as how colors are mixed. Windows CE supports the background graphics mode and drawing mode. A background graphics mode defines how background colors are mixed with window or screen colors for text and bitmap operations. A drawing mode defines how foreground colors are mixed with window or screen colors for pen, brush, bitmap, and text operations.

Windows CE initializes a device context with default graphic modes. You can get the current background mix mode with the GetBkMode function and set it with the SetBkMode function. In Windows CE, the background mix mode affects the appearance of text and certain pen types. You can set the foreground mix mode with the SetROP2 function. The foreground mix mode controls how the brush or pen colors and the image colors combine. SetROP2 returns the mix mode for the last foreground mix mode.

You can change the viewpoint origin from its default starting point in the upper-left corner of the screen with the SetViewportOrgEx function.

Note Windows CE does not support multiple mapping modes. The only mapping mode is MM_TEXT, which maps logical coordinates to the physical coordinates in a 1:1 ratio from left to right and top to bottom.


Creating Bitmaps

A bitmap is an array of bits that, when mapped to a rectangular pixel array on an output device, creates an image. Use bitmaps to create, modify, and store images.

Windows CE supports two types of bitmaps, device-dependent bitmaps (DDBs) and device-independent bitmaps (DIBs). A device-dependent bitmap does not have its own color table and can therefore be properly displayed only by a device with the same display memory organization as the one on which it was created. A device-independent bitmap, on the other hand, generally has its own color table, and therefore can be displayed on multiple devices. It is recommended that you use DIBs in Windows CE-based applications.

To create a device-independent bitmap

  1. Call the CreateDIBSection function.

    CreateDIBSection creates a DIBSection, which contains all the information necessary for displaying the DIB.

  2. Call the SelectObject function to select the DIBSection into the device context.
  3. Select the DIBSection again and call DeleteObject to delete the DIBSection when finished.

The BITMAPINFO structure defines the dimensions and color information for a DIB. This structure consists of a BITMAPINFOHEADER structure and an array of two or more RGBQUAD structures. The BITMAPINFOHEADER structure contains information about the dimensions and color format of a DIB. Each RGBQUAD structure defines one bitmap color. The BITMAPINFO structure must include a color table if the images are palettized with formats of 1, 2, 4, or 8 bits per pixel (bpp). For a 16 bpp or 32 bpp non-palettized image, the color table must be three entries long; the entries must specify the value of the red, green, and blue bitmasks. Because GDI ignores the color table for 24 bpp bitmaps, you should store the image pixels in RGB format.

To create a device-dependent bitmap

  1. Call CreateCompatibleDC to create a memory device context.

    This function creates a device context compatible with the specified device. The device context contains a single-bit array that serves as a placeholder for a bitmap.

  2. Call CreateBitmap or CreateCompatibleBitmap to create the bitmap. If calling CreateCompatibleBitmap, be sure that you specify a screen device context rather than a memory device context; otherwise, you will get a device context to a 1 bpp device.
  3. Call SelectObject to select the bitmap into the device context.

    Windows CE then replaces the single-bit array with an array large enough to store color data for the specified rectangle of pixels.

When you draw using the handle returned by CreateCompatibleDC, the output does not appear on a device’s drawing surface; rather, it is stored in memory. To copy the image stored in memory to a display device, call the BitBlt function. BitBlt copies the bitmap data from the bitmap in the source device context into the bitmap in the target device context. In this case, the source device context is the memory device context, and the target device context is the display device context. Thus, when BitBlt completes the transfer, the image appears on the screen. By reversing the source and target device contexts, you can call BitBlt to transfer images from the screen into memory.

The following code example shows how to create a memory device context, how to use a CreateCompatibleBitmap to create a bitmap, and how to use BitBlt to copy bitmap data from the source device context to the target device context.

VOID BitmapDemo (HWND hwnd)
{

  HDC hDC,                  // Handle to the display device context
      hDCMem;               // Handle to the memory device context
  HBITMAP hBitmap,          // Handle to the new bitmap
          hOldBitmap;       // Handle to the old bitmap
  static int iCoordinate[200][4];
  int i, j,
      iXSrc, iYSrc,         // x and y coordinate of source
                            // rectangle's upper-left corner
      iXDest, iYDest,       // x and y coordinate of destination
                            // rectangle's upper-left corner
      iWidth, iHeight;      // Width and height of the bitmap

  // Retrieve a handle to a display device context for the client
  // area of the window (hwnd).
  if (!(hDC = GetDC (hwnd)))
    return;

  // Create a memory device context compatible with the device.
  hDCMem = CreateCompatibleDC (hDC);

  // Retrieve the width and height of windows display elements.
  iWidth = GetSystemMetrics (SM_CXSCREEN) / 10;
  iHeight = GetSystemMetrics (SM_CYSCREEN) / 10;

  // Create a bitmap compatible with the device associated with the
  // device context.
  hBitmap = CreateCompatibleBitmap (hDC, iWidth, iHeight);

  // Select the new bitmap object into the memory device context.
  hOldBitmap = SelectObject (hDCMem, hBitmap);

  for (i = 0; i < 2; i++)
  {
    for (j = 0; j < 200; j++)
    {
      if (i == 0)
      {
        iCoordinate[j][0] = iXDest = iWidth * (rand () % 10);
        iCoordinate[j][1] = iYDest = iHeight * (rand () % 10);
        iCoordinate[j][2] = iXSrc = iWidth * (rand () % 10);
        iCoordinate[j][3] = iYSrc = iHeight * (rand () % 10);
      }
      else
      {
        iXDest = iCoordinate[200 - 1 - j][0];
        iYDest = iCoordinate[200 - 1 - j][1];
        iXSrc = iCoordinate[200 - 1 - j][2];
        iYSrc = iCoordinate[200 - 1 - j][3];
      }

      // Transfer pixels from the source rectangle to the destination
      // rectangle.
      BitBlt (hDCMem, 0, 0, iWidth, iHeight, hDC,  iXDest, iYDest,
              SRCCOPY);
      BitBlt (hDC,  iXDest, iYDest, iWidth, iHeight, hDC,  iXSrc, iYSrc,
              SRCCOPY);
    }
  }

  // Select the old bitmap back into the device context.
  SelectObject (hDC, hOldBitmap);

  // Delete the bitmap object, free all resources associated with it.
  DeleteObject (hBitmap);

  // Delete the memory device context and the display device context.
  DeleteDC (hDCMem);
  DeleteDC (hDC);

  return;
}

Bit block transfer (blit) functions, such as BitBlt, can be used to modify as well as transfer bitmaps. These functions modify a destination bitmap by combining it with a pen, a brush, and, in some cases, a source bitmap, in a format specified by a raster operation (ROP) code. Each ROP code specifies a unique logical pattern for combining graphic objects. For example, the SRCCOPY ROP simply copies a source bitmap to a destination bitmap, while the MERGECOPY ROP merges the colors of a source rectangle with a specified pattern.

The following table shows the ROP code types.

ROP type
Description
ROP2 | Combines a pen or brush with a destination bitmap in one of 16 possible combinations.
ROP3 | Combines a brush, a source bitmap, and a destination bitmap in one of 256 possible combinations.
ROP4 | Uses a monochrome “mask” bitmap to combine a foreground ROP3 and a background ROP3. The mask uses zeros and ones to indicate the areas where each ROP3 will be used.

When the source and destination bitmaps are different sizes, you can call the StretchBlt function to perform a blit between the two bitmaps. StrechBlt copies a bitmap from a source rectangle into a destination rectangle, stretching or compressing the bitmap to fit the destination rectangle.

Additionally, you can call the PatBlt function to paint a selected rectangle using a selected brush and an ROP3 code. You can also call the TransparentImage to transfer all portions of a bitmap except for those drawn in a specified transparent color. This function is especially useful for transferring non-rectangular images, such as icons.

Note Windows CE supports arbitrary bit pixel formats, which enable you to use blit functions between bitmaps with different pixel depths.


Working with Colors

Some display devices and printers display only monochrome images; others use hundreds, thousands, or millions of colors. You should design your applications to display properly on devices with a variety of color capabilities.

The color range available to a display device is determined primarily by the pixel depth it supports. Pixel depth is measured in bits per pixel (bpp). Each bit can have a value of 1 or 0. A pixel depth of 1 bit per pixel accepts only two values. While these values are usually black and white, you can use any two colors available on your device. A pixel depth of 2 bpp has four possible color values or all possible combinations of 0 and 1 with two bits. In general, the number of possible colors is equal to 2 raised to the power of the pixel depth. Windows CE supports pixel depths of 1, 2, 4, 8, 16, 24, and 32 bpp.

You can call the GetDeviceCaps function, which specifies the NUMCOLORS value, to discover the number of colors a device supports. Usually, this count corresponds to a physical property of the output device, such as the number of inks in the printer or the number of distinct color signals the display adapter can transmit to the monitor. For modes greater than 8 bpp, use the return value of GetDeviceCaps with the BITSPIXEL value. The number of supported colors for that mode is 2 raised to the power of the return value.

Windows and applications use parameters and variables having the COLORREF type to pass and store color values. You can extract the individual values of the red, green, and blue components of a color value by using the GetRValue, GetGValue, and GetBValue macros, respectively. Use the RGB or PALLETERGB macros to create a color value from individual red, green, and blue component values.

If you request a color that the display device cannot generate, Windows CE approximates it with a color that the device can generate. For example, if you attempt to create a red pen for a black-and-white printer, you will receive a black pen instead—Windows CE uses black as the approximation for red.

You can discover how Windows CE approximates a specified color by calling the GetNearestColor function. The function takes a color value and returns the color value of the closest matching color the device can generate.

Windows handles colors in bitmaps differently than colors in pens, brushes, and text. Compatible bitmaps, created by calling the CreateBitmap or CreateCompatibleBitmap function, retain color information in a device-dependent format. Specifically, DDBs use the color values of the device on which they were created.

The DIBSection structure representing a DIB retains color information as either color values or color palette indexes. If color values are used, the colors might be approximated as necessary. Although Windows CE does not approximate colors identified by indexes, the colors in the bitmap could change if the palette changes.

Note An offscreen DIB section should have the same color table as the screen; otherwise, GDI will have to perform a time-consuming color translating blit when the DIB section is transferred to the screen. For grayscale devices, the color table should be 0x000000, 0x808080, 0xc0c0c0, and 0xFFFFFF. For color devices, the application should first query the standard palette to determine its color display capabilities, and then build a matching color table.


Working with Palettes

A palette is a collection that contains the colors that can be displayed on an output device. Palettes are used by devices that can display only a subset of their potential colors at any specified time.

Windows CE has no standard color palette and creates a default palette each time you create a device context. Windows CE bases this palette on the device capabilities. Most devices have 256 colors. Display devices, for example, often use the 16 standard VGA colors and 4 other Windows CE-defined colors. Printer devices may use other default colors. If you specify a pen or text color not in the default palette, Windows CE approximates the display color with the closest color in the palette. When displaying bitmaps, Windows CE assigns colors to a bitmap based on the bitmap’s associated color table. If an image has no color table, Windows uses the color palette in the currently selected device context.

You cannot change the entries in the default palette. However, you can create your own logical palette and select the palette into a device context in place of the default palette. You can use logical palettes to define and use colors that meet your specific needs. Windows CE enables you to create multiple logical palettes. You can attach each logical palette to a unique device context or you can switch between multiple logical palettes in a single device context.

Windows CE supports both palettized and non-palettized color display devices. Palettized devices have a color palette coded directly into their display card. Non-palettized devices use pixel bit values in the video memory to directly define colors in terms of their red, green, and blue values. You can use the GetDeviceCaps function to determine if a device supports color palettes.

To create a logical color palette

  1. Assign values to the members of a LOGPALETTE structure and pass a pointer to the structure to the CreatePalette function.

    The function returns a handle to a logical palette with the values specified in the LOGPALETE structure.

  2. Call the SelectPalette function to select the palette into the current device context.
  3. Call the RealizePalette function to make the system palette the same as the palette in the current device context.

Your logical palette should have just enough entries to represent the colors you need. You can call the GetDeviceCaps function with the SIZEPALETTE index to retrieve the maximum palette size associated with a device.

When working with palettes, you can change the colors in an existing logical palette as well as retrieve color values. The following table shows how to modify the color palette.

To
Call
Change the colors in an existing logical palette. | SetPaletteEntries
Update the display after changing or creating a palette. | RealizePalette
Retrieve the color values for a logical palette. | GetPaletteEntries
Retrieve the value in a specified logical palette that most closely matches a specified color value. | GetNearestPaletteIndex
Delete a logical palette. Be sure that the logical palette is not selected into a device context when you delete it. | DeleteObject

Note The GetSystemPaletteEntries and RealizePalette functions will fail if the device associated with the selected device index does not have a settable palette. Call GetDeviceCaps to find out if the device has a settable palette.

Windows CE does not arbitrate between the palettes of the background and foreground applications. The application running in the foreground controls the system palette. Applications that use colors other than standard Windows colors might not display properly when they run in the background. Windows CE does not perform any color matching operations between the foreground and background applications; therefore, background applications cannot successfully call RealizePalette.

The following code example shows how to create color palettes.

HPALETTE CreateScalePalette (HDC hDC, int iColor)
{
  HPALETTE hPalette = NULL;   // Handle of the palette to be created
  LPLOGPALETTE lpMem = NULL;  // Buffer for the LOGPALETTE structure
                              // which defines the palette
  int index,                  // An integer
     iReserved,               // Number of reserved entries in the
                              // system palette
     iRasterCaps;             // Raster capabilities of the display
                              // device context
  // Retrieve the raster capabilities of the display device context.
  // Check if it is capble of specifing a palette-based device, then
  // determine the number of entries in the logical color palette.

  iRasterCaps = GetDeviceCaps (hDC, RASTERCAPS);
  iRasterCaps = (iRasterCaps & RC_PALETTE) ? TRUE : FALSE;

  if (iRasterCaps)
  {
    iReserved = GetDeviceCaps (hDC, NUMRESERVED);
    iPalSize = GetDeviceCaps (hDC, SIZEPALETTE) - iReserved;
  }
  else
    iPalSize = GetDeviceCaps (hDC, NUMCOLORS);

  // If there cannot be any entries in the logical color palette, exit.
  if (iPalSize <= 0)
  {
    MessageBox (g_hwndMain,
                TEXT("Palette can't be created, there can't be any")
                TEXT("entries in it."),
                TEXT("Info"),
                MB_OK);
    goto exit;
  }

  // Allocate a buffer for the LOGPALETTE structure.
  if (!(lpMem = (LOGPALETTE *) LocalAlloc (LMEM_FIXED,
                sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * iPalSize)))
    goto exit;

  lpMem->palNumEntries = (WORD) iPalSize;
  lpMem->palVersion = (WORD) 0x0300;

  switch(iColor)
  {
    case 0:            // Red color component only
      for (index = 0; index < iPalSize; index++)
      {
        lpMem->palPalEntry[index].peRed   = (BYTE) index;
        lpMem->palPalEntry[index].peGreen = 0;
        lpMem->palPalEntry[index].peBlue  = 0;
        lpMem->palPalEntry[index].peFlags = 0;
      }
      break;

    case 1:            // Green color component only
      for (index = 0; index < iPalSize; index++)
      {
        lpMem->palPalEntry[index].peRed   = 0;
        lpMem->palPalEntry[index].peGreen = (BYTE) index;
        lpMem->palPalEntry[index].peBlue  = 0;
        lpMem->palPalEntry[index].peFlags = 0;
      }
      break;

    case 2:            // Blue color component only
      for (index = 0; index < iPalSize; index++)
      {
        lpMem->palPalEntry[index].peRed   = 0;
        lpMem->palPalEntry[index].peGreen = 0;
        lpMem->palPalEntry[index].peBlue  = (BYTE) index;
        lpMem->palPalEntry[index].peFlags = 0;
      }
      break;

    case 3:            // Gray scale palette
    default:
      for (index = 0; index < iPalSize; index++)
      {
        lpMem->palPalEntry[index].peRed   = (BYTE) index;
        lpMem->palPalEntry[index].peGreen = (BYTE) index;
        lpMem->palPalEntry[index].peBlue  = (BYTE) index;
        lpMem->palPalEntry[index].peFlags = 0;
      }
      break;
  }

  // Create the palette.
  hPalette = CreatePalette (lpMem);

  // Free the memory object lpMem.
  LocalFree ((HLOCAL) lpMem);

exit:
  return hPalette;
}


Working with Pens

In Windows CE, a pen is a graphic object for drawing lines. Drawing applications use pens to draw freehand lines and straight lines. Computer-aided design (CAD) applications use pens to draw visible lines, section lines, center lines, and so on. Word processing and desktop publishing applications use pens to draw borders and rules. Spreadsheet applications use pens to designate trends in graphs and to outline bar graphs and pie charts.

Windows CE stock pens include the BLACK_PEN and the WHITE_PEN, which each draw a solid, 1-pixel-wide line in their respective color, and the NULL_PEN, which does not draw. You obtain the stock pens with the GetStockObject function.

You call the CreatePen or CreatePenIndirect function to create a custom pen with a unique color, width, or pen style.

The following table shows the pen styles supported by Windows CE.

Pen style
Description
PS_SOLID | Draws a solid line
PS_DASH | Draws a dashed line
PS_NULL | Does not draw a line

Windows CE supports wide pens and dashed pens, but does not support wide, dashed pens, dotted pens, inside frame pens, geometric pens, or pen endcap styles.

You can create a pen with a unique color by storing the RGB value that specifies the color that you want in a COLORREF structure and passing this structure address to the CreatePen or CreatePenIndirect function. In the case of CreatePenIndirect, the COLORREF pointer is incorporated into the LOGPEN structure, used by CreatePenIndirect.

Note The wide pen requires significant GDI computation. To improve the performance of a handwriting application, use a standard-size pen.

The following code example shows how to use pen functions.

#define NUMPT  200

HDC hDC;            // Handle to the display device context
HPEN hPen,          // Handle to the new pen object
     hOldPen;       // Handle to the old pen object
RECT rect;          // A RECT structure contains the window’s client
                    // area coordinates
int index,
    iCBHeight;      // Command bar height
POINT ptAxis[2],    // Two dimensional POINT structure array
     ptSine[NUMPT]; // 200 dimensional POINT structure array
static COLORREF g_crColor[] = {
                        0x000000FF,0x0000FF00,0x00FF0000,0x0000FFFF,
                        0x00FF00FF,0x00FFFF00,0x00FFFFFF,0x00000080,
                        0x00008000,0x00800000,0x00008080,0x00800080,
                        0x00808000,0x00808080,0x000000FF,0x0000FF00,
                        0x00FF0000,0x0000FFFF,0x00FF00FF,0x00FFFF00};

// Retrieve a handle to a display device context for the client
// area of the window (hwnd).
if (!(hDC = GetDC (hwnd)))
  return;

// Retrieve the coordinates of the window’s client area.
GetClientRect (hwnd, &rect);

// Retrieve the height of the command bar in pixels.
iCBHeight = CommandBar_Height (g_hwndCB);

// Assign the axis points coordinates in pixels.
ptAxis[0].x = 0;
ptAxis[0].y = iCBHeight + (rect.bottom - iCBHeight) / 2;
ptAxis[1].x = rect.right - 1;
ptAxis[1].y = ptAxis[0].y;

// Assign the sine wave points coordinates in pixels.
for (index = 0; index < NUMPT; ++index)
{
  ptSine[index].x = index * rect.right / NUMPT;
  ptSine[index].y = (long) (iCBHeight + \
                            (rect.bottom - iCBHeight) / 2 * \
                            (1 - sin (8 * 3.14159 * index / NUMPT)));
}

// Create a dash pen object and select it.
hPen = CreatePen (PS_DASH, 1, g_crColor[5]);
hOldPen = SelectObject (hDC, hPen);

// Draw a straight line connecting the two points.
Polyline (hDC, ptAxis, 2);

// Select the old pen back into the device context.
SelectObject (hDC, hOldPen);

// Delete the pen object, free all resources associated with it.
DeleteObject (hPen);

// Create a solid pen object and select it.
hPen = CreatePen (PS_SOLID, 3, g_crColor[5]);
hOldPen = SelectObject (hDC, hPen);

// Draw a sine wave shaped polyline.
Polyline (hDC, ptSine, NUMPT);

// Select the old pen back into the device context.
SelectObject (hDC, hOldPen);

// Delete the pen object, free all resources associated with it.
DeleteObject (hPen);

// Release the device context.
ReleaseDC (hwnd, hDC);

return;


Working with Brushes

In Windows CE, a brush is a graphic object for painting the interior of closed shapes. Drawing applications use brushes to paint shapes; word processing applications use brushes to paint rules; CAD applications use brushes to paint the interiors of cross-section views; and spreadsheet applications use brushes to paint graphs.

When you call one of the functions that create a brush, such as CreatePatternBrush, it returns a handle to a logical brush. When you select the logical brush into the device context with the SelectObject function, the device driver for the corresponding device creates the physical brush used for painting.

When you call a painting function, GDI maps a pixel in the brush bitmap to the window origin of the client area. The window origin is the upper-left corner of the window client area. The coordinates of the mapped pixel are called the brush origin. The default brush origin is the upper-left corner of the brush bitmap, at the coordinates (0, 0). You can call the SetBrushOrgEx function to change the location of the brush origin by a specified number of pixels. To make the changes effective, you must call the SelectObject function to select the modified brush.

Windows CE supports three types of logical brushes: stock brushes, solid brushes, and pattern brushes.

The types of stock brushes consist of the white brush, black brush, gray brush, light gray brush, dark gray brush, and the null brush—which does not paint. Call the GetStockObject function to select one of the stock brushes.

Windows CE maintains 21 stock brushes whose colors are used in window elements such as menus, scroll bars, and buttons. You can obtain a handle to a system stock brush with the GetSysColorBrush function. Furthermore, you can retrieve the color window element with the GetSysColor function, and set a color corresponding to a window element with the SetSysColors function.

A solid brush contains 64 pixels of the same color in an 8 x 8 pixel square. You can call the CreateSolidBrush function to create a solid brush of a specified color. To paint with your solid brush, call SelectObject to select it into a specified device context.

You can create a pattern brush from an application-defined bitmap or a device-independent bitmap. To create a logical pattern brush, you must create a bitmap and then call the CreatePatternBrush or CreateDIBPatternBrushPt function, supplying a handle that identifies the bitmap or DIB.

Windows CE does not support hatched brushes. However, you can achieve the effect of a hatched brush by calling the CreateDIBPatternBrushPt function to create a pattern brush with the hatch pattern that you want.

The following code example shows how to use brush functions.

HDC hDC;            // Handle to the display device context
HRGN hRgn;          // Handle to a region object
HBRUSH hBrush;      // Handle to a brush object
RECT rect;          // A RECT structure contains the window’s client
                    // area coordinates
static COLORREF g_crColor[] = {
                        0x000000FF,0x0000FF00,0x00FF0000,0x0000FFFF,
                        0x00FF00FF,0x00FFFF00,0x00FFFFFF,0x00000080,
                        0x00008000,0x00800000,0x00008080,0x00800080,
                        0x00808000,0x00808080,0x000000FF,0x0000FF00,
                        0x00FF0000,0x0000FFFF,0x00FF00FF,0x00FFFF00};

// Retrieve a handle to a display device context for the client
// area of the window (hwnd).
if (!(hDC = GetDC (hwnd)))
  return;

// Retrieve the coordinates of the window’s client area.
GetClientRect (hwnd, &rect);

// Create a rectangular region.
hRgn = CreateRectRgn (0, 0, rect.right, rect.bottom);

// Create a solid brush.
hBrush = CreateSolidBrush (g_crColor[0]);

// Fill the region out with the created brush.
FillRgn (hDC, hRgn, hBrush);

// Delete the rectangular region.
DeleteObject (hRgn);

// Delete the brush object, free all resources associated with it.
DeleteObject (hBrush);

// Release the device context.
ReleaseDC (hwnd, hDC);

return;


Printing

Windows CE does not send printing commands directly to output devices. Rather, it passes all output information to device drivers, which, in turn, send the information to display devices and printers. Windows CE has a small footprint in part because it does not need to maintain hard-coded routines for interfacing with multiple output devices.

Most applications strive for “what you see is what you get” (WYSIWYG) output. Ideally WYSIWYG would mean that text drawn with a specified font and size on the screen would have a similar appearance when printed. However, it is almost impossible to obtain true WYSIWYG output, partly because of differences between video and printer technologies.

To obtain a WYSIWYG effect when drawing text, call the CreateFont function and specify the typeface name and logical size of the font you would like to draw with, and then call the SelectObject function to select the font into a printer device context. Windows CE will select a physical font that is the closest possible match to the specified logical font.

To start a print job

  1. Call the SetAbortProc function to establish an abort procedure.

    The abort procedure should include a modeless dialog box that enables a user to cancel a print job.

  2. Initialize the necessary variables registered in your AbortProc function.
  3. Display a modeless Cancel dialog box.
  4. Call the StartDoc function to start the print job.

Once you have started the print job, you can define individual pages in the document by calling the StartPage and EndPage functions and embedding the appropriate calls to GDI drawing functions within this bracket. After you have defined the last page, you can close the document and end the print job with the EndDoc function.

Windows CE does not have a print manager. It will not spool or print more than a single copy of a document.

Note The display driver does all the rendering in Windows CE, and scales the output to the printer resolution. If you intend to print text, you should use a system with TrueType fonts, because raster fonts cannot be scaled to different printer resolutions without significantly compromising text quality.


Working with Regions

In Windows CE, a region is a rectangle that can be filled, painted, framed, and tested to see if it contains a particular point.

You create a region by calling CreateRectRgn or CreateRectRgnIndirect. These functions return a handle identifying the new region. When using the CreateRectRgn and CreateRectRgnIndirect functions, use values for regions that can be represented by 16-bit integers, because that is how region data is stored in Windows CE. Once you have a handle to a region, you can select the region into a device context with the SelectObject function.

You can perform a variety of operations on a region. You can paint or invert its interior, draw a frame around it, retrieve its dimensions, and test whether or not a particular point lies within it. The following table shows what tasks you can perform on regions.

To
Call
Determine if two regions are equal in size and shape | EqualRgn
Paint the interior of a region with a specified brush | FillRgn
Retrieve the dimensions of a region’s bounding rectangle | GetRgnBox
Move a region a specified number of logical units | OffsetRgn
Retrieve data describing a region | GetRegionData
Determine if a point is inside a specified region | PtInRegion

You can also combine or compare a region with another region by calling the CombineRgn function. The following table shows how you can call the CombineRgn function to combine two regions together.

Value
Description
RGN_AND | The intersecting parts of two original regions define a new region.
RGN_COPY | A copy of the first of the two original regions defines a new region.
RGN_DIFF | The part of the first region that does not intersect the second defines a new region.
RGN_OR | The two original regions define a new region.
RGN_XOR | Those parts of the two original regions that do not overlap define a new region.

Windows CE does not support the InvertRgn or InvertRect functions. You can achieve the effect of InvertRect by calling the PatBlt function with an ROP code of DSTINVERT.


Clipping Regions

You can use clipping regions to restrict your output to a specified subregion of the client area. To use a clipping region, you must select it into the device context associated with the display device.

Clipping is used in Windows CE in a variety of ways. Word processing and spreadsheet applications clip keyboard input to keep it from appearing in the margins of a page or spreadsheet. Computer-aided design (CAD) and drawing applications clip graphics output to keep it from overwriting the edges of a drawing or picture.

Some device contexts provide a predefined or default clipping region. For example, the device context created by the BeginPaint function contains a predefined rectangular clipping region that corresponds to the invalid rectangle to be repainted. However, the device contexts created by the CreateDC and GetDC functions contain empty clipping regions; clipping is only done to keep graphics output in the window client area.

You can perform a variety of operations on clipping regions. Some of these operations require a handle identifying the region and some do not. For example, you can perform the following operations directly on a device context clipping region:

  • Determine if part of the client area intersects a region by calling the RectVisible function.
  • Exclude a rectangular part of the client area from the current clipping region by calling the ExcludeClipRect function.
  • Combine a rectangular part of the client area with the current clipping region by calling the IntersectClipRect function.

After obtaining a handle identifying the clipping region, you can perform any operation common with regions, such as:

  • Combine a copy of the current clipping region with a second region by calling the CombineRgn function.
  • Compare a copy of the current clipping region to a second region by calling the EqualRgn function.
  • Determine if a point lies within the interior of a copy of the current clipping region by calling the PtInRegion function.


Creating Shapes and Lines

Windows CE enables you to draw lines and a variety of filled shapes. A line is a set of highlighted pixels on a raster display or a set of dots on a printed page identified by two points: a starting point and an ending point. In Windows CE, the pixel located at the starting point is always included in the line, and the pixel located at the ending point is always excluded.

You can draw a series of connected line segments by calling the Polyline function and supplying an array of points that specify the ending point of each line segment.

Note Windows CE does not support the LineTo or the MoveToEx function. However, you can call the Polyline function in Windows CE to achieve the same results that you would get in Windows-based desktop platforms if you called the MoveToEx function and then made one or more calls to the LineTo function.

Filled shapes are geometric shapes that Windows CE outlines with the current pen and fills with the current brush. Windows CE supports four filled shapes: ellipse, polygon, rectangle, and round rectangle, a rectangle with rounded corners.

A Windows CE-based application uses filled shapes in a variety of ways. Spreadsheet applications, for example, use filled shapes to construct charts and graphs; drawing applications enable users to draw figures and illustrations using filled shapes.

An ellipse is a closed curve defined by two fixed points — f1 and f2 — such that the sum of the distances — d1 + d2 — from any point on the curve to the two fixed points is constant. The following illustration describes an ellipse drawn by using the Ellipse function.

When calling Ellipse, supply the coordinates of the upper-left and lower-right corners of the ellipse bounding rectangle. A bounding rectangle is the smallest rectangle that completely surrounds the ellipse.

A polygon is a filled shape with straight sides. Windows CE uses the currently selected pen to draw the sides of the polygon, and the current brush to fill it. Windows CE fills all enclosed regions within the polygon with the current brush.

Note Windows CE does not support multiple fill modes. When it fills a polygon, it fills all subareas created by intersecting lines within the polygon.

A rectangle is a four-sided polygon whose opposing sides are parallel and equal in length, and whose interior angles are 90 degrees. Although you can call the Polygon function to draw a rectangle if you supply it with all four sides, it is easier to call the Rectangle function. This function requires only the coordinates of the upper-left and the lower-right corners.

You can call the RoundRect function to draw a rectangle with rounded corners. Supply this function with the coordinates of the lower-left and upper-right corners of the rectangle, and the width and height of the ellipse used to round each corner.

You can call the FillRect function to paint the interior of a rectangle. You can call the FillRgn function to fill a region using the specified brush.

Because Windows CE does not support paths, many line drawing functions available on Windows-based desktop platforms are not available in Windows CE. Windows CE does not support functions to draw an arc, beizer curve, chord, pie, polypolygon, or polypolyline. However, you can approximate these shapes using existing Windows CE drawing functions. For example, you can create an arc by calling the Ellipse function with an appropriately defined clipping region.

Note The Ellipse and RoundRect functions require significant GDI computation. To increase your application performance, use these functions sparingly.

The following code example shows how to create shapes and lines using the Rectangle, Ellipse, Polygon, and RoundRect functions.

VOID DrawRandomObjects (HWND hwnd)
{
  HDC hDC;                  // Handle to the display device context
  RECT rect;                // A RECT structure
  POINT pt[4];              // Four dimensional POINT structure array
  HBRUSH hBrush,            // Handle to the new brush object
         hOldBrush;         // Handle to the old brush object
  TCHAR szDebug[80];        // A debug message string

  int x1, y1, x2, y2, x3, y3, x4, y4,
                            // The coordinates of four points
      iRed, iGreen, iBlue,  // Indicate the Red, Green, Blue component
                            // color of the brush
      iObject;              // An integer indicates the type of objects

  // Retrieves the handle to the display device context.
  if (!(hDC = GetDC (hwnd)))
    return;

  // Retrieves the coordinates of a window's client area.
  GetClientRect (hwnd, &rect);

  // Avoid divide by zero errors when the window is small.
  if (rect.right == 0)
    rect.right++;
  if (rect.bottom == 0)
    rect.bottom++;

  // Generate three random numbers.
  iRed = rand() % 255;
  iGreen = rand() % 255;
  iBlue = rand() % 255;

  // Create a solid brush object and selects it into the device context.
  hBrush = CreateSolidBrush (RGB(iRed, iGreen, iBlue));

  if (hOldBrush = SelectObject (hDC, hBrush))
  {
    // Randomly generates four points.
    x1 = rand() % rect.right;
    y1 = rand() % rect.bottom;
    x2 = rand() % rect.right;
    y2 = rand() % rect.bottom;
    x3 = rand() % rect.right;
    y3 = rand() % rect.bottom;
    x4 = rand() % rect.right;
    y4 = rand() % rect.bottom;

    // Randomly generates an integer indicates the type of objects.
    iObject = rand() % 4;

    switch (iObject)
    {
      case 0:
        wsprintf (szDebug, TEXT("Rectangle(%d ,%d, %d, %d)\n"),
                  x1, y1, x2, y2);

        // Draws a rectangle.
        Rectangle (hDC, x1, y1, x2, y2);

        break;

      case 1:
        wsprintf (szDebug, TEXT("Ellipse(%d, %d, %d, %d)\n"),
                  x1, y1, x2, y2);

        // Draws an ellipse.
        Ellipse (hDC, x1, y1, x2, y2);

        break;

      case 2:
        wsprintf (szDebug, TEXT("RoundRect (%d, %d, %d, %d, %d, %d)\n"),
                  x1, y1, x2, y2, x3, y3);

        // Draws a rectangle with rounded corners.
        RoundRect (hDC, x1, y1, x2, y2, x3, y3);

        break;

      case 3:
        pt[0].x = x1;
        pt[0].y = y1;
        pt[1].x = x2;
        pt[1].y = y2;
        pt[2].x = x3;
        pt[2].y = y3;
        pt[3].x = x4;
        pt[3].y = y4;

        wsprintf (szDebug,
                  TEXT("Chord(%d, %d, %d, %d, %d, %d, %d, %d)\n"),
                  x1, y1, x2, y2, x3, y3, x4, y4);

        // Draws a polygon.
        Polygon(hDC, pt, 4);

        break;

      default:
        break;
    }

    // Select the old brush into the device context.
    SelectObject (hDC, hOldBrush);

    // Delete the brush object, free all resources associated with it.
    DeleteObject (hBrush);
  }

  ReleaseDC (hwnd, hDC);
  return;
}


Creating Text and Fonts

In Windows CE, a font is a collection of glyphs that share a common design. A font is characterized by its typeface, style, and size. The font typeface determines the specific characteristics of the glyphs, such as the relative width of the thick and thin strokes used in any specified character. The style determines the font weight and slant. Font weights can range from thin to black. Slants can be roman (upright) or italic. The size of a font is the distance from the bottom of a lowercase “g” to the top of an adjacent uppercase “M,” measured in points. A point is approximately one seventy-second of an inch.

In Windows CE, fonts are grouped into families, which share common stroke width characteristics. Fonts within a family are distinguished by size and style. The following table shows the font families.

Font family name
Description
Decorative | Specifies a novelty font, for example, Old English.
Dontcare | Specifies a generic family name. This name is used when information about a font does not exist or does not matter.
Modern | Specifies a monospace font with or without serifs. Monospace fonts are usually modern; examples include Pica, Elite, and Courier New.
Roman | Specifies a proportional font with serifs, for example, Times New Roman.
Script | Specifies a font designed to look like handwriting; examples include Script and Cursive.
Swiss | Specifies a proportional font without serifs, for example, Arial.

These family names correspond to constants found in the Wingdi.h header file: FF_DECORATIVE, FF_DONTCARE, FF_MODERN, FF_ROMAN, FF_SCRIPT, and FF_SWISS. Use these constants to create, select, or retrieve font information.


Working with TrueType and Raster Fonts

Windows CE supports raster and TrueType font technologies, but accepts only one type to be used on a specified system. The choice of TrueType or raster font type is made when the system is designed, and cannot be changed by an application.

The differences between raster and TrueType fonts have to do with the way the glyph for each character or symbol is stored in the respective font resource file. A raster font glyph is a tiny bitmap that represents a single character size. Because the bitmaps for each glyph in a raster font are designed for a specific resolution on a particular device, raster fonts are generally considered device-dependent.

A TrueType font glyph contains outlines and hints. Windows CE uses these hints to adjust the outlines used to draw the glyphs. These hints and the respective adjustments are based on the amount of scaling used to reduce or increase the size of the glyph. Because TrueType characters can be scaled up or down and still retain their original appearance, they are considered device-independent.

A font’s glyphs are stored in a font resource file. A font resource file for a raster font is stored in a .fot file. TrueType fonts have two files, a short .fot header file, and a .ttf file that contains the actual data. When installing a TrueType font, you do not have to install the .fot file, only the .ttf file.


Enabling Font Linking

Windows CE provides font linking capability, making it possible to link one or more fonts, called linked fonts, to another font, called the base font. Once you link, fonts, you can use the base font to display code points that do not exist in the base font, but do exist in one of the linked fonts. For example, linking a Hangeul font and a Japanese font to a Latin font would give you the ability to display both Korean and Japanese in the Latin font using Unicode text APIs.

Note Font linking can only add glyphs to a base font; it is not possible to override or replace glyphs in the base font.

If font linking is enabled on your device, you can examine the registry by enumerating the subkeys of the registry key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\FontLink\SystemLink to determine the mappings of linked fonts to base fonts. You can add additional linkings by creating additional subkeys. The following code example shows how to add an additional linking.

"base font face name" = "path and file to link to," "face name of the font to link"


Creating End User Defined Characters

Although Windows CE defines thousands of characters, you may need to define you own set of characters. Use an end user defined character (EUDC) any time you need to define a character or glyph for a device. Always associate an EUDC with a double-byte character set (DBCS) and a TrueType font. When you create an EUDC, choose a reserved DCBS value. Applications use DBCS values to identify the EUDC. Windows CE uses DBCS values to locate the shape and style information in the corresponding TrueType font. The shape and style information specifies how to draw the EUDC.

To create an EUDC

  1. Choose a character value in the specified range or ranges of reserved characters.
  2. Use an EUDC editor to create the shape and style of the character.
  3. Add the shape and style information to the TrueType font in the entry that corresponds to the selected character value.

To associate an EUDC Font with another font

  1. Copy the EUDC font to a folder.

    The EUDC font has a .tte extension.

  2. Call EnableEUDC (FALSE).
  3. Modify the [HKEY_CURRENT_USER\EUDC] registry key.
  4. Create a subkey under [HKEY_CURRENT_USER\EUDC].
  5. In the subkey that you created in step 4, enter the font path that contains the EUDCs.

    For example, enter Tahoma=\windows\test03.tte in the subkey to link the Tahoma font with the test03.tte font located in the Windows directory.

  6. Call EnableEUDC (TRUE).

Note Before creating EUDC entries in the registry, enumerate the existing EUDC settings to ensure that you do not overwrite entries defined for the Windows CE device.


Installing and Using Fonts

Call the AddFontResource function to load a font from a font resource file. When you finish using an installed font, call the RemoveFontResource function to remove the font. Whenever you add or delete a font resource, you should call the SendMessage function to send a WM_FONTCHANGE message to all top-level windows in the system. This message notifies other applications that an application has modified the internal font table by adding or removing a font. You do not need to call AddFontResources to create or realize system fonts.

There are two stages to selecting a font. In the first stage, you specify the ideal font you would like to use. This theoretical font is called a logical font. In the second stage, an internal algorithm finds the physical font that is the closest match to your specified logical font. A physical font is a font stored on the device or in the operating system. This process is called font mapping.

To use a font

  1. Call the EnumFontFamilies function to build a list the available fonts. This is especially useful when you want to determine available fronts from a specified font family or typeface.
  2. Use the values returned by the font enumeration function to initialize the members of a LOGFONT structure.
  3. Create the logical font by calling the CreateFontIndirect function and passing it a pointer to the initialized LOGFONT structure.
  4. Select the logical font into the current device context with the SelectObject function.

When you call SelectObject, Windows CE loads the physical font that most closely matches the logical font specified in the LOGFONT structure.

When initializing the members of the LOGFONT structure, be sure that the lfCharSet member specifies a specific character set. This member is used in the font mapping process and the results will be inconsistent if this member is not initialized correctly. If you specify a typeface name in the lfFaceName member of the LOGFONT structure, be sure that the lfCharSet value contains an appropriate value.

Windows CE keeps a table containing all the fonts available for application use. When you call CreateFontIndirect, Windows CE chooses a font from this table.

Windows CE provides six standard logical fonts. You can use the GetStockObject function to obtain a standard font. The following table shows the standard font values.

Value
Description
ANSI_FIXED_FONT | Specifies a monospace font based on the Windows character set, usually represented by a Courier font
ANSI_VAR_FONT | Specifies a proportional font based on the Windows character set, usually represented by the MS Sans Serif font
DEVICE_DEFAULT_FONT | Specifies the preferred font for the specified device, usually represented by the System font for display devices
OEM_FIXED_FONT | Specifies a monospace font based on an OEM character set.
SYSTEM_FONT | Specifies the System font. This is a proportional font based on the Windows character set, and is used by the operating system to display window titles, menu names, and text in dialog boxes. The System font is always available. Other fonts are available only if installed.


Enumerating Fonts

You can enumerate the available fonts by calling the EnumFonts or EnumFontFamilies function. These functions send information about the available fonts to a callback function that the application supplies. The callback function stores the information in the LOGFONT structure and in either the NEWTEXTMETRIC structure for TrueType fonts or the TEXTMETRIC structure for raster fonts. By using the information returned from these functions, you can limit the user’s choices to available fonts only.

The EnumFontFamilies function is similar to the EnumFonts function, but includes some extra features for use with TrueType fonts. The EnumFontFamilies function enumerates all the styles associated with a specified typeface, and not simply the bold and italic attributes. For example, when the system includes a TrueType font called Courier New Extra-Bold, EnumFontFamilies lists it with the other Courier New fonts.

Note Despite its name, EnumFontFamilies actually enumerates the styles associated with a specified typeface—for example, Arial—rather than a font family, such as Swiss.

If you do not supply a typeface name, the EnumFonts and EnumFontFamilies functions supply information about one font in each available typeface. To enumerate all the fonts in a device context, you can specify NULL for the typeface name, compile a list of the available typefaces, and then enumerate each font in each typeface.


Formatting Text

Windows CE provides a complete set of functions to format and draw text in an application client area and on a page of printer paper.

The default text color for a display device context is black; the default background color is white; and the default background mode is OPAQUE. Call the SetTextColor and GetTextColor functions to respectively set and retrieve the color of text drawn in the client area of a window or printed by a color printer. Call the SetBkColor and GetBkColor functions to respectively set or retrieve the background color. Call the SetBkMode and the GetBkMode functions to respectively set or retrieve the background mode. The background mode specifies the logical method for combining the selected background color with the current video display colors.

You can call the GetTextExtentPoint32 function to compute the advance width and height of a string of text. You can call the GetTextMetrics function to retrieve a font’s logical dimensions. You can call the GetDeviceCaps function to determine the dimensions of an output device.


Drawing Text

After you have selected a font, set your text-formatting options, and computed the necessary character width and height values for a string of text, you can draw characters and symbols using either the DrawText or ExtTextOut function. When you call one of these functions, the operating system passes the call to the graphics engine, which in turn passes the call to the appropriate device driver.

In most cases ExtTextOut is faster than DrawText. However, there are some instances when DrawText is more efficient, as in the case where you need to draw multiple lines of text within the borders of a rectangular region. DrawText does not work with rotated text.


Programming Waveform-Audio

Working with Sound

Adding sound to an application can make it more efficient to use. By using sounds to draw attention at critical points, you can help users avoid mistakes and notify them when a time-consuming operation concludes. This section describes two different ways to play sound in an application. The first part describes how to use the PlaySound function. PlaySound provides all the necessary capabilities for playing waveform-audio sounds on a Windows CE-based device. The latter part of this section describes how to use the Waveform Audio API. This interface gives an application exact control over waveform audio input/output (I/O) devices.


Using the PlaySound Function

You can use the PlaySound function to play waveform audio sound files, as long as the sound can be stored in available device memory. The following list describes three ways to play sound with PlaySound:

  • As a file name
  • As a system alert, using the alias stored in the registry
  • As a resource identifier

You can also use the sndPlaySound function to play waveform audio files. However, sndPlaySound offers a subset of the PlaySound feature set. Windows CE maintains sndPlaySound for backward compatibility.


Using the PlaySound Function with Waveform Audio Files

In Windows CE, most waveform audio files use the .wav file name extension. The following code example shows how to play the \Sounds\Bells.wav file.

PlaySound (TEXT("\\SOUNDS\\BELLS.WAV"), NULL, SND_SYNC);

If the specified file does not exist or the file cannot be stored in available device memory, PlaySound plays the default system sound. If you have not defined a default system sound, PlaySound fails without playing a sound. The following code example shows how to specify that the default sound should not be played.

PlaySound (TEXT("\\SOUNDS\\BELLS.WAV"), NULL, SND_SYNC | SND_NODEFAULT);

The example uses the SND_NODEFAULT flag in the fdwSound parameter to prohibit the application from playing the default sound.


Using PlaySound with Registry-Specified Sounds

The PlaySound function also plays sounds referred to by a key name in the registry. Using this feature, you can let users assign selected or custom sounds to system alerts and warnings. Sounds associated with system alerts and warnings are called sound events.

To play a sound event

  • Call PlaySound with pszSound pointing to a string containing the name of the registry entry that identifies the sound.

    The following example shows how to use PlaySound to play a sound event.

    PlaySound (TEXT("MouseClick"), NULL, SND_SYNC);

Note PlaySound plays the default system sound if the registry entry specified in pszSound does not exist or if the sound file cannot be stored in available device memory.

The sndPlaySound function searches the registry for a key name matching lpszSound before attempting to load a file with the pszSound name. The Playsound function accepts flags that specify the location of the sound.


Using PlaySound with a Resource Identifier

To play a sound stored as a resource, use the PlaySound function. Although you can use sndPlaySound to play a resource sound, you must find, load, lock, unlock, and free the resource; in contrast, PlaySound completes these tasks in a single line of code.

To include a .wav file as a resource in an application

  • Add the following entry to the application resource script (.rc) file
    soundName WAVE \sounds\bells.wav

    The name soundName is a placeholder for a name you supply to refer to the wave resource sound. Wave resources are loaded and accessed like other application-defined Windows resources.

The following example shows how to use the PlaySound function to play the wave resource sound.

PlaySound (TEXT("soundName"), hInst, SND_RESOURCE | SND_ASYNC);

In contrast, the following code example shows how to use the sndPlaySound function to play a wave resource sound.

BOOL PlayResource (LPTSTR lpName)
{
    BOOL bRtn;
    LPTSTR lpRes;
    HANDLE hResInfo, hRes;

    // Find the WAVE resource.
    hResInfo = FindResource (hInst, lpName, "WAVE");

    if (hResInfo == NULL)
      return FALSE;

    // Load the WAVE resource.
    hRes = LoadResource (hInst, hResInfo);

    if (hRes == NULL)
      return FALSE;

    // Lock the WAVE resource and play it.
    lpRes = LockResource (hRes);

    if (lpRes != NULL)
  {
      bRtn = sndPlaySound (lpRes, SND_MEMORY | SND_SYNC | SND_NODEFAULT);
      UnlockResource (hRes);
    }
    else
      bRtn = 0;

    // Free the WAVE resource and return success or failure.
    FreeResource (hRes);

    return bRtn;
}

To play a wave resource sound by using sndPlaySound, pass the function a pointer to a string containing the resource name, as shown in the following example.

PlayResource (TEXT("soundName"));


Using the Waveform Audio Interface

This section describes the Waveform Audio interface. An application uses this interface to gain the greatest possible control over audio I/O devices. Specifically, this section discusses the following features of the Waveform Audio API:

  • Querying and opening waveform audio I/O devices
  • Writing waveform-audio data
  • Allocating audio data blocks
  • Playing waveform-audio files
  • Handling errors with audio functions
  • Deallocating waveform audio data blocks
  • Closing waveform audio output devices


Querying and Opening Waveform Audio I/O Devices

To correctly record or play a sound, first determine what drivers your Windows CE-based device has available for audio I/O, then open those drivers for recording or playback.


Querying Audio I/O Devices

The following table shows functions that retrieve the number of audio devices on a Windows CE-based device.

Function
Description
waveInGetNumDevs | Retrieves the number of waveform audio input devices present in a system
waveOutGetNumDevs | Retrieves the number of waveform audio output devices present in a system

After you determine how many devices are present in a system, you can query the capabilities of each device. The following table shows the functions and structures that retrieve this information.

Function
Description
Returned structure
waveInGetDevCaps | Retrieves the capabilities of a specified waveform audio input device | WAVEINCAPS
waveOutGetDevCaps | Retrieves the capabilities of a specified waveform audio output device | WAVEOUTCAPS

The waveInGetDevCaps and waveOutGetDevCaps fill the dwFormats member of the WAVEINCAPS and WAVEOUTCAPS structures with flags describing the standard supported sound formats for a specified audio I/O device. Waveform audio I/O devices also support nonstandard capabilities. You can also use the waveInOpen or the waveOutOpen function to determine if a waveform audio I/O device supports a specific format.

To determine if a waveform audio I/O device supports a standard or nonstandard format

  1. Specify the format you wish to query in the WAVEFORMATEX structure.
  2. Pass this structure into waveInOpen or waveOutOpen using the pwfx parameter.
  3. Call waveInOpen or waveOutOpen with the WAVE_FORMAT_QUERY flag set.

    This call does not open the device. Instead, the function returns a message declaring whether or not the audio device supports the specified format.

Note While WAVEFORMATEX supersedes PCMWAVEFORMAT and WAVEFORMAT, Windows CE maintains both structures for backward compatibility.

The following code example shows how to use the waveOutOpen function with the WAVE_FORMAT_QUERY flag to determine whether a waveform audio device supports a specified format.

MMRESULT IsFormatSupported (LPWAVEFORMATEX pwfx, UINT uDeviceID)
{
    return waveOutOpen (NULL,               // ptr can be NULL for query
                        uDeviceID,          // The device identifier
                        pwfx,               // Defines requested format
                        NULL,               // No callback
                        NULL,               // No instance data
                        WAVE_FORMAT_QUERY); // Query only, do not open
}

The preceding example determines whether the specified waveform-audio output device supports a specified waveform-audio format. It returns MMSYSERROR_NOERROR if the device supports the format, WAVERROR_BADFORMAT if the device does not support the format, and one of the other MMSYSERROR codes for other errors.

This technique for determining nonstandard format support also applies to waveform audio input devices. The only difference is that the function will use waveInOpen instead of waveOutOpen to query for format support.

To determine if any waveform audio I/O devices on a system support a particular waveform audio data format, use the technique illustrated in the previous example. However, you must specify the WAVE_MAPPER constant in the uDeviceID parameter.


Opening Waveform Audio Output Devices

In addition to querying the capabilities of a device, you can use the waveOutOpen or waveInOpen functions to open a waveform audio I/O device for recording or playback. These functions open the device associated with the specified device identifier and return a pointer to an open device handle.

Both waveOutOpen and waveInOpen choose the device best able to play the specified data format. Windows CE identifies waveform audio I/O devices using a device identifier. The OS determines the device identifier implicitly from the number of devices present in a system. Device identifiers range from zero through the number of devices present minus one. For example, the valid device identifiers for a system with two waveform audio output devices are 0 and 1.

In addition to the device number, the waveOutOpen and waveInOpen functions require a pointer to a memory location. The functions fill the memory location with a device handle. Use this device handle to identify the open waveform audio I/O device when calling other audio functions. The following list describes the differences between a device identifier and a device handle:

  • The operating system determines a device identifier implicitly from the number of devices present on a system. The system obtains this number using the waveInGetNumDevs or waveOutGetNumDevs function.
  • The operating system returns a device handle after opening a device driver with the waveInOpen or waveOutOpen function.


Allocating Audio Data Blocks

Once you have determined the capabilities of your Windows CE-based device, you can allocate memory for your audio data blocks. Using the WAVEHDR structure, allocate memory for the waveInAddBuffer and waveOutWrite functions to play sound. The following table shows functions that prepare headers.

Function
Description
waveInPrepareHeader | Prepares a waveform-audio input data block
waveOutPrepareHeader | Prepares a waveform-audio output data block

Before passing an audio data block to a device driver with waveInAddBuffer or waveOutWrite, call waveInPrepareHeader or waveOutPrepareHeader on the data block. Then pass the data block to waveInAddBuffer or waveOutWrite.


Playing Waveform Audio Files

After successfully opening a waveform-audio output device driver and preparing the header file, you can begin playing a sound. Windows provides the waveOutWrite and waveInAddBuffer functions for sending data blocks to a waveform audio output device.

Use the WAVEHDR structure to specify the waveform-audio data block you are sending by using the waveOutWrite or waveInAddBuffer function. This structure contains a pointer to a locked data block, the length of the data block, and some flags. After you send a data block to an output device, wait until the device driver is finished with the data block before freeing it. If you are sending multiple data blocks, monitor the completion of data blocks to know when to send additional blocks.

Note Unless the waveform audio input and output data is contained in a single data block, an application must continually supply the device driver with data blocks until recording or playback is complete.


Retrieving the Current Playback Position

Monitor the current playback position within the data block by using the waveOutGetPosition or the waveInGetPosition function and the MMTIME structure. Use the MMTIME structure to represent time in one or more different formats, including milliseconds, samples, MIDI song pointer, or the Society of Motion Picture and Television Engineers (SMPTE) time formats. The wType member specifies the format used to represent time.

To retrieve the current playback position

  1. Set the wType member in the MMTIME structure to the desired time format.
  2. Call waveOutGetPosition or waveInGetPosition.

    While calling this function, pass the MMTIME structure in the pmmt parameter.

  3. Check the wType member after the call to see whether the requested time format is supported.

    If the device does not support the time format, the device driver specifies the time in an alternate time format and changes wType to the selected time format.

Most waveform audio devices support samples as the preferred time format. Thus, a waveform audio device usually describes its current position in a .wav file as the number of samples from the beginning of the waveform-audio file.


Stopping, Pausing, and Restarting a Waveform Audio I/O Device

While recording or playing waveform audio, you may want to stop, pause, or restart the audio I/O device. The following table shows the functions that control these capabilities.

Function
Description
waveInStop | Stops recording on a waveform audio input device
waveOutPause | Pauses playback on a waveform audio output device
waveInReset | Stops recording on a waveform audio input device and marks all pending data blocks as done
waveOutReset | Stops playback on a waveform audio output device and marks all pending data blocks as done
waveInStart | Begins recording on a waveform audio input device
waveOutRestart | Resumes playback on a paused waveform audio output device

Generally, the waveform audio I/O device begins recording or playing as soon as the waveOutWrite or waveInAddBuffer function sends the first waveform-audio data block.

To delay activation of waveform-audio output

  1. Call waveOutPause to pause the waveform-audio output device.
  2. Call waveOutWrite.

    This sends the first data block to the waveform-audio output device. However, the device is paused by the previous call to waveOutPause.

  3. Call waveOutRestart to begin playback.

Once the device begins playback, it might not respond to waveOutPause immediately. Depending on the device driver, the device may finish playing the current data block before pausing.

You can also delay activation of waveform-audio input. Call waveInAddBuffer as needed to allocate space for the audio input. The device will not begin recording until you call waveInStart.

Once the device stops recording or playback in response to a waveOutreset or waveInReset command, you cannot restart playback with waveOutRestart or waveInStart. Instead, resume recording or playback by sending the next data block with waveOutWrite or waveInAddBuffer and waveInStart.


Looping Playback

You can control looping playback on a waveform-audio output device with the WAVEHDR structure passed into waveOutWrite.

To loop playback

  1. Set the WHDR_BEGINLOOP flag in the dwFlags member of the WAVEHDR structure.

    This flag defines the beginning data block of a message loop.

  2. Set the number of loops in the dwLoops member of the same WAVEHDR structure.
  3. Pass this and the rest of your data blocks to waveOutWrite.
  4. Pass the final data block to waveOutWrite with the WHDR_ENDLOOP flag set in the WAVEHDR structure.

    This causes your device to begin looping.

  5. Use waveOutBreakLoop to break out of the loop prematurely.

Note Loop a single block by setting the WHDR_BEGINLOOP and WHDR_ENDLOOP flags in the same WAVEHDR structure.


Changing the Volume of Waveform Audio Playback

The following table shows Windows CE functions that query and set the volume level of waveform audio output devices.

Function
Description
waveOutGetVolume | Returns the current volume level of the specified waveform-audio output device
waveOutSetVolume | Sets the volume level of the specified waveform audio output device

Volume is specified in a DWORD value. In a stereo audio format, the upper 16 bits specify the relative volume of the right channel. The lower 16 bits specify the relative volume of the left channel. For devices that do not support separate volume control for the different channels, Windows CE uses the lower 16 bits to specify the volume level and ignores the upper 16 bits.

Windows CE uses volume ranges from 0x0 (silence) through 0xFFFF (maximum volume) and interprets these ranges logarithmically. The perceived volume increase is the same when increasing the volume level from 0x5000 through 0x6000 as it is from 0x4000 through 0x5000.


Changing Pitch and Playback Rate

Some waveform audio I/O devices can vary the pitch and the playback rate of waveform audio data. Like other device capabilities, you can query your waveform audio I/O device with the waveOutGetDevCaps function to determine if your device supports either of these capabilities.

The following table describes the functions used to query and set waveform audio pitch and playback rates.

Function
Description
waveOutGetPitch | Retrieves the pitch for the specified waveform audio output device
waveOutGetPlaybackRate | Retrieves the playback rate for the specified waveform audio output device
waveOutSetPitch | Sets the pitch for the specified waveform audio device
waveOutSetPlaybackRate | Sets the playback rate for the specified waveform audio output device

These functions change the pitch and playback rates by a factor specified with a fixed-point number packed into a DWORD value. The upper 16 bits specify the integer part of the number; the lower 16 bits specify the fractional part. The following table shows three examples of this style of packaging.

Value
Packaged Value
1.5 | 0x00018000L
0.75 | 0x0000C000L
1.0 | 0x00010000L

A value of 1.0 means the pitch or playback rate is unchanged.

Although changing the pitch and changing the playback rate seem similar for the user, they are implemented quite differently. Because the device driver controls the playback rate, altering the playback rate does not require any specialized hardware. However, the driver does not change the sample rate. Instead, the driver skips or synthesizes samples. For example, the driver could skip every other sample if an application changed the playback rate by a factor of two. In contrast, changing the pitch requires specialized hardware. When an application alters the pitch, the application does not specifically alter the playback or sample rate.


Handling Errors with Audio Functions

The waveform audio functions return a nonzero value when an error occurs. Windows CE provides functions that convert these error values into textual descriptions of the errors. The application must still examine the error values to determine how to proceed, but can use a textual description in a dialog box to describe an error to the user. The following table shows the functions used to retrieve textual descriptions of audio error values.

Function
Description
waveInGetErrorText | Retrieves a textual description of a specified waveform audio input error
waveOutGetErrorText | Retrieves a textual description of a specified waveform audio output error

The only audio functions that do not return error values are waveInGetNumDevs and waveOutGetNumDevs. These functions return 0 if no devices are present in a system or if they encounter any errors.


Using Windows Messages to Manage Waveform Audio Playback

You can send a variety of Windows CE messages to a windows procedure function to manage waveform audio playback. The following table shows these messages.

Message
Description
MM_WIM_CLOSE | Sent when the waveOutClose or the waveInClose function closes a device
MM_WIM_DATA | Sent when the device driver finishes with a data block sent by waveOutWrite or waveInAddBuffer
MM_WIM_OPEN | Sent when waveOutOpen or waveInOpen opens a device

MM_WIM_DATA is the most useful message in this table. When MM_WIM_DATA signals a completed data block, you can clean up and free that data block. Unless you need to allocate memory or initialize variables, you probably do not need to process the MM_WIM_OPEN or MM_WIM_CLOSE messages.

Like other windows messages, these window messages have a wParam and an lParam parameter associated with them. The wParam always specifies a handle to the open waveform audio output device. For the MM_WIM_DATA message, lParam specifies a pointer to a WAVEHDR structure. This structure identifies a completed data block. The MM_WIM_CLOSE and MM_WIM_OPEN messages do not use lParam.

The following code example shows how to process the MM_WIM_DATA message.

// WndProc--Main window procedure
LRESULT FAR PASCAL WndProc (HWND hWnd, UINT msg, WPARAM wParam,
                            LPARAM lParam)
{
  switch (msg)
  {
    case MM_WIM_DATA:
      // A waveform audio data block has been played and can now be
      // freed.
      waveOutUnprepareHeader ((HWAVEOUT)wParam, (LPWAVEHDR)lParam,
                              sizeof (WAVEHDR));
      // Free hData memory
      waveOutClose ((HWAVEOUT)wParam);

      break;
  }

  return DefWindowProc (hWnd, msg, wParam, lParam);
}

This example assumes that the application does not play multiple data blocks, so it can close the output device after playing a single data block.


Deallocating Memory Blocks

An application must be able to determine when a device driver is finished with the data block so the application can unprepare and free the memory associated with the data block and header structure. The following list describes several ways to determine when a device driver is finished with a data block:

  • By specifying a callback function to receive a message sent by the driver when it is finished with a data block
  • By using an event callback
  • By specifying a window or thread to receive a message sent by the driver when it is finished with a data block
  • By polling the WHDR_DONE bit in the dwFlags member of the WAVEHDR structure sent with each data block

To use a callback function for driver messages

  1. Specify the CALLBACK_FUNCTION flag in the fdwOpen parameter of the waveInOpen or the waveOutOpen function.
  2. Specify the address of the callback in the dwCallback parameter of the waveInOpen or the waveOutOpen function.

Messages sent to a callback function are similar to messages sent to a window, except that they have two DWORD parameters instead of one UINT and one DWORD parameter.

The following list describes techniques for passing instance data from an application to a callback function:

  • Pass the instance data using the dwInstance parameter of the function that opens the device driver
  • Pass the instance data using the dwUser member of the WAVEHDR structure that identifies an audio data block sent to a device driver

Note If you need more than 32 bits of instance data, pass a pointer to a structure containing the additional information.

To use an event callback

  1. Retrieve the handle of an event from CreateEvent.
  2. Specify CALLBACK_EVENT for the fdwOpen parameter in your call to waveOutOpen.
  3. Use waveOutPrepareHeader or waveInPrepareHeader to prepare the data block.
  4. Call ResetEvent with the event handle retrieved by CreateEvent.

    This action creates a non-signaled event.

  5. Send the waveform audio data block to the driver.
  6. Call WaitForSingleObject, specifying as parameters the event handle and a time-out value of INFINITE.

    This call should be inside a loop that checks whether the WHDR_DONE bit is set in the dwFlags member of the WAVEHDR structure.

Because event callbacks do not receive specific close, done, or open notifications, an application might have to check the status of the process that it is waiting for after the event occurs. It is possible for a number of tasks to be completed by the time WaitForSingleObject returns.

To use a window callback function

  • Call waveInOpen or waveOutOpen with the fdwOpen parameter set to CALLBACK_WINDOW and a window handle passed in the dwCallback parameter.

To use a callback thread

  • Call waveInOpen or waveOutOPen with the fdwOpen parameter set to CALLBACK_THREAD and a thread handle passed in the dwCallback parameter.

Note Messages sent to the window or thread callback are specific to the audio I/O device type used.

In addition to using a callback function, you can poll the dwFlags member of a WAVEHDR structure to determine when an audio I/O device is finished with a data block. Sometimes it is better to poll dwFlags than to wait for another mechanism to receive messages from the drivers. For example, after calling the waveOutReset or waveInReset function to release pending data blocks, you can immediately poll dwFlags for WHDR_DONE to be sure that the data blocks have been released.

Once you determine that the function has released the data block, call the waveInUnprepareHeader or waveOutUnprepareHeader function to unprepare the header file. After you have unprepared the header file, you can deallocate the memory normally.


Closing Waveform Audio Output Devices

After you have finished playing a waveform-audio file and deallocating the associated header files, call the waveOutClose or waveInClose function to close the output device. If you call these functions while an application is still using a waveform-audio file, the close operation fails and the function returns an error value indicating that the device is not closed. If you do not want to wait for recording or playback to end before closing the device, call the waveOutReset or waveInReset function.


Receiving User Input

User input is the means by which a user communicates with a Windows CE-based device. The original equipment manufacturer (OEM) determines the specific combination of input devices supported by your platform. Different platforms support different input devices. For example, some platforms, such as the Palm-size PC, support a touch screen, rather than a keyboard, for text entry. Other platforms may include handwriting recognition software in place of, or in addition to, a keyboard. Windows CE supports the following types of user input:

  • Keyboard
  • Mouse
  • Touch screen and stylus
  • Input panel
  • Handwriting recognition


Receiving Keyboard Input

The keyboard is an important means of user input on many Windows CE-based devices. Windows CE maintains a device-independent keyboard model that enables it to support a variety of keyboards. The OEM usually determines the keyboard layout for a specified Windows CE-based device.

At the lowest level, each key on the keyboard generates a scan code when pressed and released. The scan code is a hardware-dependent number that identifies the key. Unlike Windows-based desktop platforms, Windows CE has no standard set of keyboard scan codes. Your application should rely only on supported scan codes for the target platform.

The keyboard driver translates or maps each scan code to a virtual key code. The virtual key code is a hardware-independent number that identifies the key. Because keyboard layouts vary from language to language, Windows CE offers only the core set of virtual key codes found on all keyboards. This core set includes Latin letters, numbers, and a few critical keys, such as the function and arrow keys. Keys not included in the core set also have virtual key code assignments, but their values vary from one keyboard layout to the next. You should depend only on the virtual key codes in the core set.

In addition to mapping, the keyboard driver determines which characters the virtual key generates. A single virtual key generates different characters depending on the state of other keys, such as the SHIFT and CAPS LOCK keys. Do not confuse virtual key codes with characters. Although many of the virtual key codes have the same numerical value as one of the characters that the key generates, the virtual key code and the character are two different elements. For example, the same virtual key generates the uppercase “A” character and the lowercase “a” character.

After translating the scan code into a virtual key code, the device driver creates a keyboard message containing scan code, virtual key code, and character data, and then places the message in the system message queue. The system removes the message from the system message queue and posts it to the message queue of the appropriate thread. Each thread maintains its own active window and focus window. The active window is a top-level window. The focus window is either the active window or one of its descendants. At any one time one thread in the system is considered the foreground thread. The active window of this thread is considered the foreground window.

The device driver places keyboard messages in the message queue of the foreground thread. The thread message loop pulls the message from the queue and sends it to the window procedure of the thread focus window. If the focus window is NULL, the window procedure of the active window receives the message.

The following illustration shows the keyboard input model.


Working with Threads

There are a number of ways a thread can become the foreground thread. If an application calls the SetForegroundWindow function and specifies a top-level window, the thread that owns the window becomes the foreground thread and the window becomes its active window. This function also moves the window to the top of the z-order. You can use SetForegroundWindow on any top-level window.

In most cases, if the user taps a window, the system will place that window in the foreground. The thread that created the window becomes the foreground thread. If the foreground window is hidden or destroyed, the system designates another window as the foreground window. In that case, the new foreground window thread becomes the foreground thread. Call the GetForegroundWindow function to get the current foreground window.

In general, an application thread does not need to set the foreground window explicitly. This is usually done by the system as the user selects and closes windows. Call the SetActiveWindow function to activate a window. If the calling thread is the foreground thread, then the new active window automatically becomes the foreground window. When the activation changes, the system sends a WM_ACTIVATE message to both the deactivated and activated windows. A thread can call the GetActiveWindow function to access its active window.

An application thread calls the SetFocus function to move the focus between its windows. When the focus changes, the system sends a WM_KILLFOCUS message to the window losing the focus. It sends a WM_SETFOCUS message to the window gaining the focus.

The system ensures that the focus window is always the active window or a descendent of the active window. If the focus changes to a window with a different top-level ancestor, the system first changes the activation, and then changes the focus.


Processing Keyboard Messages

A window receives keyboard input in the form of keystroke messages and character messages. Keystroke messages control window behavior, and character messages determine the text displayed in a window.

Windows CE generates a WM_KEYDOWN or WM_SYSKEYDOWN message when a user presses a key. If the user holds a key down long enough to start the keyboard repeat feature, the system will generate repeated WM_KEYDOWN or WM_SYSKEYDOWN messages. When the user releases a key, a WM_KEYUP or WM_SYSKEYUP message is generated.

The system makes a distinction between system keystrokes and nonsystem keystrokes. System keystrokes produce system keystroke messages, WM-SYSKEYDOWN and WM-SYSKEYUP. Nonsystem keystrokes produce nonsystem keystroke messages, WM-KEYDOWN and WM-KEYUP.

System keystroke messages are generated when the user types a key in combination with the ALT key or when the user types a key and the focus is NULL. If the focus is NULL, the keyboard event is delivered to the active window. These messages have the WM_SYS prefix in the message name. System keystroke messages are primarily used by the system rather than by an application. The system uses them to provide its built-in keyboard interface to menus and to enable the user to control which window is active. If a window procedure processes system keyboard messages, it should pass the message to the DefWindowProc function. Otherwise, all system operations involving the ALT key disable whenever that window has the keyboard focus.

The window procedure of the window that has the keyboard focus receives all keystroke messages. However, an application that responds to keyboard input typically processes WM_KEYDOWN messages only.

When the window procedure receives the WM_KEYDOWN message, it should examine the virtual-key code that accompanies the message to determine how to process the keystroke. The virtual-key code is contained in the message’s wParam parameter.

The lParam parameter of a keystroke message contains additional data about the keystroke that generated the message. The following table describes the additional keystroke data required by the lParam parameter.

Data
Description
Repeat count | Specifies the number of times the keystroke was repeated as a result of the user holding down the key.
Scan code | Gives the hardware-dependent key scan code.
Context code | Value is 1 if the ALT key was pressed, and 0 if released.
Previous key state | Value is 1 if the pressed key was previously down, and 0 if previously up. Value is 1 for WM_KEYDOWN and WM_SYSKEYDOWN keystroke messages generated by the automatic repeat feature.
Transition state | Value is 1 if the key was released, or 0 if pressed.

Typically, an application processes only keystrokes generated by non-character keys. The following code example shows the window procedure framework that a typical application uses to receive and process keystroke messages.

case WM_KEYDOWN:
      switch (wParam)
      {
        case VK_HOME:
          // Insert code here to process the HOME key
          // ...
          break;

        case VK_END:
          // Insert code here to process the END key
          // ...
          break;

        case VK_INSERT:
          // Insert code here to process the INS key
          // ...
          break;

        case VK_F2:
          // Insert code here to process the F2 key
          // ...
          break;

        case VK_LEFT:
          // Insert code here to process the LEFT ARROW key
          // ...
          break;

        case VK_RIGHT:
          // Insert code here to process the RIGHT ARROW key
          // ...
          break;

        case VK_UP:
          // Insert code here to process the UP ARROW key
          // ...
          break;

        case VK_DOWN:
          // Insert code here to process the DOWN ARROW key
          // ...
          break;

        case VK_DELETE:
          // Insert code here to process the DEL key
          // ...
          break;

        default:
          // Insert code here to process other non-character keystrokes
          // ...
          break;
      }


Processing Character Messages

When a user types a character, Windows CE does not automatically generate a character message. It generates a keystroke message. To translate a keystroke message into a corresponding character message, the application message loop must call TranslateMessage. This function examines the virtual-key code of a keystroke message and, if the code corresponds to a character, places a character message into the message queue. The character message is removed and dispatched on the next iteration of the message loop.

The message loop should call the TranslateMessage function to translate every message, and not just keystroke messages. Although TranslateMessage has no effect on other types of messages, using it ensures that keyboard input is translated correctly. The following code example shows how to include the TranslateMessage function in a typical thread message loop.

while (GetMessage(&msg, (HWND) NULL, 0, 0))
{
   if (TranslateAccelerator(hwndMain, haccl, &msg) == 0)
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
}

Windows CE includes four character messages: WM_CHAR, WM_SYSCHAR, WM_DEADCHAR, and WM_SYSDEADCHAR. A typical window procedure ignores all character messages except WM_CHAR. The WM_CHAR message contains the character code and the flags that provide additional data about the character.

When a window procedure receives the WM_CHAR message, it should examine the character code that accompanies the message to determine how to process the character. The character code is in the message wParam parameter.

The following code example shows the window procedure framework that a typical application uses to receive and process character messages.

while (GetMessage (&msg, (HWND)NULL, 0, 0))
{
   if (TranslateAccelerator (hwndMain, haccl, &msg) == 0)
   {
      TranslateMessage (&msg);
      DispatchMessage (&msg);
   }
}


Creating and Displaying a Caret

A window that receives keyboard input typically displays the characters the user types in the window client area. A window should use a caret to indicate the position in the client area where the next character will appear. The window should create and display the caret when it receives the keyboard focus and it should hide and destroy the caret when it loses the focus. A window can perform these operations when the WM_SETFOCUS and WM_KILLFOCUS messages are processed.

Use the CreateCaret, ShowCaret, DestroyCaret, and HideCaret functions to control the visibility of the caret. Use the SetCaretPosition function to change the position of the caret as the user types.


Checking Other Keys

While processing a keyboard message, an application sometimes needs to determine the status of a key different from the one that generated the current message. You can use the GetKeyState function to determine the state of certain keys. This function returns the key’s state at the time the current message was generated. The GetAsyncKeyState function returns the state of the key at the time of the call.

The Windows CE version of these functions differ slightly from their desktop counterparts. Unlike the equivalent functions in Windows-based desktop platforms, GetKeyState supports only a limited number of keys, and GetAsyncKeyState returns the current key state even if a window in another thread has the keyboard focus.


Adding Hot Key Support

A hot key is a key combination that generates a WM_HOTKEY message. The message is routed to a particular window, regardless of whether or not that window is the current foreground window or focus window.

You define a hot key by calling the RegisterHotKey function and specifying the combination of keys that generates the WM_HOTKEY message, the handle of the window to receive the message, and the hot key identifier. When the user presses the hot key, the system places a WM_HOTKEY message in the message queue of the thread that created the specified window. The wParam parameter of the message contains the hot key identifier. Before the application terminates, it should use the UnregisterHotKey function to destroy the hot key.


Receiving Stylus Input

In many Windows CE-based applications, the user interacts with an application by using a stylus and a touch screen. The stylus and touch screen provide a direct and intuitive alternative to a mouse.

The stylus generates an input event when the user touches the screen with a stylus or moves the stylus when the tip is touching the screen. To an application, stylus input is a subset of mouse input. When a user presses and releases a stylus on a screen, the application processes these events as a click of the left mouse button. When a user moves the stylus across the screen, the application processes this as a mouse move event.

Stylus input events in a window are posted to the message queue of the thread that created the window. A window receives a stylus message when a stylus event occurs within the window client area. When the user presses the stylus to the screen, the window receives a WM_LBUTTONDOWN message. When the stylus is lifted from the screen, the window receives a WM_LBUTTONUP message. Occasionally, a window receives a WM_LBUTTONDBLCLK message instead of a WM_LBUTTONDOWN message. This occurs under the following conditions:

  • The window class was registered with the CS_DBLCLKS class style.
  • The stylus touches the screen within a certain distance of the last stylus location.
  • The stylus touches the screen within a certain time limit after the stylus last touched the screen.

If the user moves the stylus while pressing it to the screen, Windows CE generates a WM_MOUSEMOVE message.

The following table shows style input messages supported by Windows CE.

Message
Meaning
WM_LBUTTONDBLCLK | The user double-tapped the screen.
WM_LBUTTONDOWN | The user pressed the screen.
WM_LBUTTONUP | The user released the stylus from the screen.
WM_MOUSEMOVE | The user moved the stylus while the tip was pressed to the screen.

The lParam parameter of a stylus message indicates the position of the stylus tip. The low-order word is the x-coordinate and the high-order word is the y-coordinate. The coordinates are specified in the client coordinates parameter. In the client-coordinate system, all points are specified relative to the upper-left corner of the client area.

The wParam parameter contains flags that indicate the status of the other stylus buttons and the CTRL and SHIFT keys at the stylus event time. Check for these flags when the way you process a stylus event depends on the state of another stylus button or on the CTRL or SHIFT key. The following table lists the flags you can set in the wParam parameter.

Value
Meaning
MK_CONTROL | The CTRL key is down.
MK_LBUTTON | The stylus is touching the screen.
MK_SHIFT | The SHIFT key is down.


Receiving Input from an Input Panel

Windows CE-based devices that do not have a keyboard require an input method (IM) to simulate keyboard input. For this purpose, Windows CE implements an input panel architecture that functions through a touch screen. This input panel architecture is an input method that enables your application to accommodate input in multiple forms.

The Windows CE IM requires two parts: the software input panel subsystem and input methods. The primary difference between Windows CE input methods and usual keyboard input is that an additional level of interpretation is necessary in Windows CE to convert nonkeyboard input into a keyboard event. This conversion is handled by the input panel subsystem in the operating system (OS), which also manages software input methods.

The IM can display the input panel window in a specific state by using the IIMCallback::SetImInfo method. This method also changes the icon appearing on the Input Panel button. To adjust to user-initiated changes in the input panel window, applications can query the SipInfo structure for data on the input panel window size, state, and visible client area.

The input panel creates the IM through the IInputMethod interface. Once created, the IM receives user input and passes this data to the input panel through the IIMCallBack interface. After the input panel receives data from the IM, the input panel passes the data to the Graphics, Windowing, and Events Subsystem (GWES). Usually, this message is a keyboard event. GWES passes the message to your application through the standard keyboard event delivery system. The following illustration shows how an IM, an input panel, GWES, and an application communicate.

If the input panel is altered, the operating system sends out a WM_SETTINGCHANGE message to all active applications. The application can modify the input panel and IM through the SHSipInfo function.

Note To use input panel technology, your device must include Coresip.lib. This component enables any application to intialize an input method. Call the SipStatus function to be sure that Coredsip.lib is present before implementing input methods.


Programming an Input Panel

When a user accesses the input panel, Windows CE creates a dedicated input panel thread. The thread creates an input panel window and initializes the input panel. Then, the thread enters a message loop. Within the message loop, the thread responds to messages and UI requests from the input panel window. The thread also calls into the IM object. This enables the IM to create child windows in the input panel window. The content of the input panel window is determined by the current IM.

The input panel thread has a special status with the operating system. Any window that the input panel thread creates will not be obscured by other windows. Because some interface elements save and clear themselves when they lose focus, the input panel and its children do not receive the focus, even if the user is currently using the input panel.

The input panel queries the IM for data through the IInputMethod interface. The input panel can remove the current IM and replace it with a new IM. The following table lists the interface queries that an input panel sends to an IM, listed in the order received.

Method
Description
Deselect | Destroys its window and performs IM-specific cleanup procedures
Select | Creates the IM window and image list
GetInfo | Requests data regarding the new IM, including property flags and the preferred IM size
ReceiveSipInfo | Sends the IM data about the size, placement, and docked status the IM should use
RegisterCallback | Provides the IM with a pointer to the IIMRegisterCallback interface

After the input panel calls these methods, the IM should render the input panel window space and respond to user actions. For more information about programming the IM, see Programming Input Methods.

An application that uses the input panel should know the input panel state—whether the panel is visible, whether it is docked,or floating, and its size and position. This data is stored in the SIPINFO structure, accessed through the SHSipInfo function. The following code example shows how to use the SHSipInfo function to access and modify the SIPINFO structure.

BOOL LowerSip( void )
{
  BOOL fRes = FALSE;
  SIPINFO si;

  memset (&si, 0, sizeof (si));
  si.cbSize = sizeof (si);

  if (SHSipInfo (SPI_GETSIPINFO, 0, &si, 0))
  {
    si.fdwFlags &= ~SIPF_ON;
    fRes = SHSipInfo (SPI_SETSIPINFO, 0, &si, 0);
  }

  return fRes;
}

When the user changes the input panel state, the OS sends out a WM_SETTINGCHANGE message to all active applications. This message has SPI_SETSIPINFO in its wParam parameter. The following code example shows how you can use the SHSipInfo function to move the input panel on the screen in response to a WM_SETTINGCHANGE message.

WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    SIPINFO si;

    switch (msg)
    {
        case WM_SETTINGCHANGE:
            switch (wParam)
            {
                case SPI_SETSIPINFO:
                    memset (&si, 0, sizeof (si));
                    si.cbSize = sizeof (si);

                    if (SHSipInfo (SPI_GETSIPINFO, 0, &si, 0))
                    {
                        MoveWindow (
                  hwnd,
                  si.rcVisibleDesktop.left,
                  si.rcVisibleDesktop.top,
                  si.rcVisibleDesktop.right - si.rcVisibleDesktop.left,
                  si.rcVisibleDesktop.bottom - si.rcVisibleDesktop.top,
                  TRUE);
                    }
                    break;
            }
            break;
    }
  return 0;
}

A change in the active IM sends messages to all top-level applications registered to receive IM notifications. Typically, the messages go to an application that controls a display, such as a taskbar. The following table shows the messages.

Event
Windows CE sends message
Application action
IM size or position changes | WM_IM_INFO | Return 0 if the application processes the message
IM has a new icon to associate with its current state | WM_IM_INFO | Return 0 if the application processes the message


Input Methods

Programming Input Methods

An input method is an in-process COM server that implements the IInputMethod interface. Windows CE provides a default QWERTY keyboard IM to handle alpha-numeric input. The IM manages the space inside the input panel window. Within this window, the IM is responsible for screen output and responding to user input. Typically, an IM creates a child window of the input panel window. This enables the IM window to respond to user input through the input panel window; the IM does not have access to the input panel window’s WndProc function unless the IM subclasses that window. The IM usually converts user input into characters and sends those characters to the input panel through the IMCallback interface.

In order to use an IM, the user first selects the IM from the input panel dialog box or Input Panel button. The input panel dynamically loads the selected IM by calling CoCreateInstance. When the user selects a different IM, the input panel frees the existing IM by calling Release on the interface pointer. The input panel then calls IInputMethod methods to notify the IM of events and to request data. The input panel implements and exposes the IIMCallback interface. This interface lets the IM call the input panel to send keystrokes to the application through GWES. The following table describes the methods available through the IMCallback interface.

IMCallback method
Description
SetImInfo | Lets the IM change the input panel icon and the visible state of the input panel
SendVirtualKey | Modifies the global key state
SendCharEvents | Sends Unicode characters to the window with the current focus
SendString | Sends strings to the window with the current focus

In response to the input panel calls to IInputMethod, an IM creates windows in the contexts of the input panel thread. This way the input panel and the IM belong to the same message loop. For simplicity, all calls to IIMCallback should be made in the input panel thread. That is, the IM should call IIMCallback methods only in response to a call coming through an IInputMethod method.

Additionaly, the IM should not create a separate thread to implement a user interface (UI). Only the thread that responds to IInputMethod methods should create and manipulate windows. Instead, the IM can create process threads to implement a UI. However, these process threads must not call IIMCallback, because certain GWES window functions work properly only if created from the same thread that created the window.

If you develop your own IM component, you should have your setup application perform self-registration by calling the DllRegisterServer and DllUnregisterServer functions. Implement these functions in the IM server DLL. Optionally, you can set the registry values directly. The Input Panel properties dialog box does not provide any UI elements for the self-registration service.


Input Method Registry Values

Technically, any COM component that implements IInputMethod can be selected into the input panel. The IsSIPInputMethod subkey is a shortcut that presents a list of IMs to the user without loading and querying each object for the IInputMethod interface.

Input methods are installed in the system as in-process COM servers using standard COM registry keys. The key HKEY_CLAS_ROOT\CLSID contains subkeys representing COM components. The subkeys are textual representations of CLSIDs. The CLSID subkeys contain an InprocServer32 subkey with a default value. This value specifies the DLL path that implements the component. The CLSID subkeys also contain a IsSipInputMethod subkey with a default value equal to the "1" string. The following table lists examples of IM registry values.

Key
Default value
HKEY_CURRENT_USER\ CLSID\
{4a4a96d7-ae04-11d0-a4f8-00aa00a749b9}
"MS QWERTY IM"
HKEY_CURRENT_USER\ CLSID\
{4a4a96d7-ae04-11d0-a4f8-00aa00a749b9}\
InprocServer32

"\Windows\alphanum.dll"
HKEY_CURRENT_USER\ CLSID\
{4a4a96d7-ae04-11d0-a4f8-00aa00a749b9}\
IsSIPInputMethod

"1"


Handwriting Recognition

As an alternative or companion to a keyboard, you can employ a handwriting pad IM in your application for processing user-drawn numbers, letters, characters, and symbols. The Windows CE handwriting recognition engine currently recognizes all 94 characters of the ASCII character set. In addition, the engine recognizes all of the available glyph characters, known as ideographs, for Chinese, Japanese, and Korean, and produces their corresponding Unicode output.

This section describes the implementation of the Hwx (pronounced wix) functions used to process handwritten characters entered by the user on the input panel.


Recognizing a Hand-Drawn Character

The recognition of hand-drawn alpha-numeric characters and glyphs occurs within the boundaries of a box, or multiple of boxes, that you define for the input panel of your target device. The structure of the applicable language determines how multiple boxes are arranged, for example, from left to right and top to bottom for English. By moving a stylus in a predefined pattern within the box, the handwriting recognition engine employed by your application can process and recognize this input, one character or glyph at a time, to produce the corresponding Unicode output.


Setting Up the HWXGUIDE Structure

You define the size and position of the box or boxes used for character entry within the HWXGUIDE structure. For interpreting inked input coordinates, the HwxSetGuide function provides the recognition engine with the coordinates to the HWXGUIDE structure. The following illustration shows the dimensions of a character input box.

The base (cyBase) and midline (cyMid) in the HWXGUIDE structure are used primarily for characters and are not required for glyphs. Placement of a specific box within the target device display is defined by the cxOrigin and cyOrigin parameters. Offset parameters establish a buffer space between each box, when multiple boxes are used, to prevent the user from overwriting ink data from one box into another. In the event that you overwrite ink data, the recognition engine must determine to which box the ink data corresponds.

After initializing the handwriting recognition engine with HwxConfig, the entire recognition process begins and ends with the creation and destruction of a handwriting recognition context (HRC) object. Similar to a handle to a window (HWND), or a handle to a device context (HDC), an hrc is a unique 32-bit handle used by the recognition engine for handwriting recognition. An HRC is used for input in a single box or multiple boxes. It carries all ink data and setup parameters necessary for recognizing a glyph, one character, or multiple characters. Use HwxCreate and HwxDestroy respectively, to create and destroy an HRC object.


Processing User Input

To begin each input recognition session with a clean HRC, use the context from a preestablished HRC as a template. Each time you need a new HRC, pass the handle of the previous HRC object, excluding any previous ink data, to the recognition engine. This enables your application to save processing time by not having to recreate common HRC parameters used repeatedly in your application, such as GUIDE structures and alphabet codes.

The character recognition sequence begins when the input panel IM sends a call to HWXInput. This function adds ink to the HRC object to provide the STROKEINFO structure with the count of stylus stroke points generated. HwxProcess passes the HRC to the recognition engine where the stroke data is processed based on predetermined character recognition parameters. When the user finishes entering stroke data, your application calls HwxEndInput to tell the recognition engine no further ink will be added to the HRC.

To improve the performance of the recognition process, pass the context for the previous character to the recognition engine through HwxSetContext. If this function is not called, the recognition engine will assume that no previous context is available. HwxSetContext is called before HwxProcess. HwxProcess processes the ink received by the HRC to perform the recognition. Full character recognition occurs only after HwxEndInput is called. Results of the recognition are returned by HwxGetResults.

HwxGetResults simplifies the task of recognizing characters and glyphs drawn within the boundaries of the defined box, by responding with character alternatives on a per-box basis in one call. The following code example sets the results for ten boxes at a time, with five alternatives for each box.

HANDLE hMem = GlobalAlloc( GHND, 10 * (sizeof( BOXRESULTS )
         + (5-1) * sizeof( SYV )) );
LPBOXRESULTS rgBoxR = (LPBOXRESULTS)GlobalLock( hMem );
UINT indx = 0;
Do
{
   int iRes = HwxGetResults( hrc, 5, indx, 10, rgBoxR);
       .
       .        // Check for errors and use rgBoxR
       .
   indx += (UINT)iRes;
}
while (iRes == 10);

Once all input, processing, recognition, and output has been completed, destroy the active HRC with the HwxDestroy function.


Aiding the Recognition Process

HwxALCValid and HwxALCPriority enable the recognition engine to identify a correct character match more rapidly and with a greater accuracy by establishing a prerequisite set of recognition parameters. Use HwxALCValid if you know what the user-entered values for a specified input field will be. For example, if your input field is part of a mailing address, such as numbers only for a United States ZIP code, you can use HwxALCValid with its alc parameter set to ALC_NUMERIC so that only number values are compared by the recognition engine to match the user input.

If the user-entered values for a given field will not be limited to any one predictable set of alphabet codes (ALC), you can use HwxALCPriority. For the type of input expected, you can use HwxALCPriority to provide the recognition engine with a prioritized list of ALCs to use for comparison to user ink input.

When implemented in your application, multithreaded processing improves the recognition engine’s ability to communicate with the input thread, and thus, speeds up the recognition process. This is because the recognition of a single character can take several seconds when using an order-independent process. In multi-threaded processing, the thread that gathers ink input can control the thread used for recognition. Optimally, this enables the recognition engine to produce partial results for display while the user writes on the writing pad.


The Partial Recognition Process

Through a process called partial recognition, the recognition engine uses some or all of the user-entered stroke data to determine the resulting Unicode match. Windows CE uses partial recognition for glyphs in character sets with complex multi-stroke stylus input, such as Japanese, Chinese, and Korean. You can set up the HwxSetPartial function to accomplish partial recognition of glyphs similarly to how you set it up for character recognition. The exception is that your application calls the recognition engine multiple times for each glyph recognition. Each time a stroke is processed through the UI, you instruct the recognition engine to process using the current amount of data.

Though not an absolute requirement, multithreaded operation is the most efficient means for performing partial recognition. While one thread gathers character input, another can process data. When the two threads communicate with each other, then the thread performing character input can control the thread performing the character recognition. This gives your application the capability to produce partial results while the user is still writing. HwxSetAbort enables the gathering thread to tell the processing thread to stop, saving processing time by not using an invalid result. In this basic process, HwxSetAbort passes the recognition engine an HRC and a pointer to an address that contains the number of strokes currently input by the user. If the contents of the address do not match the number of strokes the recognition engine is trying to process, the recognition engine stops.


Performing Handwriting Recognition

The process of handwriting recognition requires your application to use the handwriting API to accomplish the following sequence of events.

Starting and Ending a Handwriting Recognition Session

  1. Call HwxConfig to initialize the recognition engine for your application.

    This occurs only once for any specified application.

  2. Create an HRC object using HwxCreate.
  3. Define the box or boxes used for processing user input using HwxSetGuide.
  4. Define recognition criteria using HwxALCValid and HwxALCPriority.
  5. Pass the previously-recognized character, if one exists, to the HRC using HwxSetContext.
  6. Call HwxInput to send ink data to the HRC as the user writes.
  7. Use HwxProcess to pass the HRC to the recognition engine for processing.

    Note Repeat step 7 for each input stroke when performing partial recognition.

  8. Call HwxResultsAvailable to obtain the number of characters recognized.
  9. Get the recognition engine results using HwxGetResults.
  10. Invalidate the current HRC with HwxDestroy to complete the recognition process.


Designing a User Interface for Windows CE

An application user interface (UI) serves two main purposes: to receive user input and to provide user output. How well your application handles these tasks depends on the target hardware platform capabilities, OS configuration, and I/O requirements.

Before designing your application, you need to ask some important questions about its interface: Will it include graphics? How will your application receive user input? Will users type commands with a keyboard, with a touch screen, with voice commands, or with buttons on a console? How will you provide feedback to the user? Will your device support an LCD screen or audio feedback?

Windows CE supports a range of devices, from the H/PC to embedded systems. Its modular feature design enables you create applications suited for a specific platform. Because UI requirements vary, this section describes general design considerations for a graphical user interface.

A well-designed user interface focuses on users and their tasks. Good user-interface design considers general design principles as well as how graphics, color, and layout influence application usability. Consider the following design concepts when creating a user-focused UI:

  • Give the user control

    Enable the user, not the computer or software, to initiate actions. Remember, the goal of the user is not to use the application, but to accomplish a task.

  • Use familiar concepts

    To increase familiarity with the interface, enable users to manipulate representations of the tasks they perform. For example, if you provide a desktop-like interface, enable users to drag icons depicting documents to an icon depicting a trash can when deleting a file. For other types of interfaces, be sure buttons and icons relate to the tasks they perform. For example, display a wrench icon to start an automotive maintenance application.

    Another way to increase user familiarity with the interface is to avoid using modes. Modes, which occur when identical commands or keystrokes perform different actions in different situations, force users to think about how the application works instead of the task at hand. Though modes are best avoided, warning boxes and message boxes are two necessary and appropriate mode types.

  • Be consistent

    Consistency makes the interface familiar and predictable, which reduces user errors and improves performance. Consistency is enhanced with components that have a similar appearance and behavior and with actions that have the same result regardless of context. For example, in a desktop environment, scroll bars operate the same way, whether the scroll bar is in a list box or window. To achieve consistency, reuse standard commands in each task.

  • Allow interactive discovery

    Empower the user to explore the interface through trial and error, but provide warnings about potential damage to the system or data. To minimize user problems, provide clear error messages and indicate appropriate actions to recover from an error. When possible, make actions reversible or recoverable.

  • Provide feedback

    Present the user with timely visual and audio cues to confirm that the software is responding to input.

  • Focus on aesthetics

    An attractive interface helps the user select appropriate information and suggests a quality application.

  • Design with simplicity

    Simple interfaces, with an uncluttered display, are easy to learn and use. Show the most important controls directly on the interface and hide the rest in menus. Reduce the number of tasks presented in a single window or screen and group related tasks together.

  • Support multiple input methods

    Provide multiple methods for performing an operation. To accomplish this, support multiple input devices if possible, and provide keyboard shortcuts or accelerators for specific tasks, if a keyboard is supported.


Designing Windows and Dialog Boxes

Many graphical user interfaces use a desktop metaphor, which simplifies common file operations by presenting them in a familiar context. Depicting files as paper documents, directories as folders, and deleted items within a trash can are examples of the desktop metaphor. Though appropriate for most applications running on an H/PC or similar device, this metaphor may not be appropriate for some embedded systems, such as an automobile navigation application or a point-of-sale device. If the desktop metaphor is not appropriate for your application, use another suitable metaphor.

Whatever metaphor you choose, provide a context for your application. When using the desktop metaphor, present objects in standard windows and dialog boxes. If using a different metaphor, you may choose to forgo using windows entirely, and present objects only in dialog boxes. If you are creating an application whose windows do not take up the full screen, design the window to be a fixed size because Windows CE does not support the resizing of windows by users.

Dialog boxes are secondary windows that contain controls and provide information to a user about actions. Windows CE supports three types of dialog boxes: application-defined dialog boxes, message boxes, and property sheets.

An application-defined dialog box helps users perform tasks specific to an application. It provides a great deal of flexibility by enabling you to place controls directly onto the body of the dialog box. This is especially useful when designing interfaces that do not use a desktop metaphor because you can design an entire application interface using only application-defined dialog boxes to house controls. When using an application-defined dialog box, include only as many controls needed for your application and space them adequately.

An application-defined dialog box can be modal or modeless. A modal dialog box requires the user to supply information or cancel the dialog box before enabling the application to continue. A modeless dialog box enables the user to supply information and return to a previous task without closing the dialog box.

A message box is a modal dialog box that displays a message and prompts for user input. It typically contains a text message and one or more predefined buttons. The following screen shot shows a modal dialog box.

The following screen shot shows a property sheet, a collection of tabbed pages that enables a user to view and modify object properties.

In a desktop metaphor, a dialog box typically contains OK and Cancel commands, which initiate a user request or dismiss the window, respectively. In Windows CE, the X button represents both the Close and Cancel commands. Follow these guidelines for using the X and OK buttons in dialog boxes:

  • When the OK and X buttons perform the same function, use the OK button, because users are more comfortable clicking the OK button than the X button to confirm an action. Be sure to disable the X button.
  • Never place an OK button both in the command bar and in the body of a dialog box because users may find this confusing. However, you can place a Cancel button in the body of a dialog box and an X button on the title bar.


Designing Menus

Menus are collections of commands, attribute selections, separators, and other selectable elements. All Windows CE menus are implemented as top-level, pop-up windows that do not support buttons. Although Windows CE supports owner-drawn menu items, it handles them as it would other menu items.

Windows CE does not support menu bars. Instead, it combines the features of a menu bar and a toolbar into one control, called a command bar, which makes efficient use of the screen space available on Windows CE-based devices. The following screen shot shows a command bar.

Windows CE supports four menu types:

  • Pop-up

    A pop-up menu is a floating menu that displays commands specific to the object selected by the user or to the object’s immediate context. A pop-up menu appears at the location on the screen where the user accessed it. It is typically used for common commands that rarely change in content and for items that require a small amount of screen space. It is recommended that you restrict the number of items in a pop-up menu to less than 10.

  • Scrolling

    A scrolling menu is a menu that adds scrolling arrows so the user can scroll the menu up and down if a menu exceeds the display area height. Scrolling menus are unique to Windows CE. With scrolling menus, you do not have to limit the size of a menu to the number of items that fit on screen.

  • Cascading

    A cascading menu is a secondary menu or submenu that appears when a certain option is selected in the parent menu. A triangular arrow next to the parent item in a menu indicates a cascading menu. Windows CE displays cascading menus in alphabetical order. If the height of a cascading menu exceeds the maximum screen height, the menu adopts a multiple-column mode on an H/PC and scrolls on a Palm-size PC to show the remaining menu items. Use a cascading menu to group related menu items or when a choice leads to a short list of related options.

  • Pull-down

    A pull-down menu contains commands accessed from a command or menu bar. It is commonly used to display text, but can also contain graphics, colors, and shading. When creating a pull-down menu, display all possible command choices on the menu. Items that cannot be chosen due to the application state should be grayed out. Use a pull-down menu to provide access to a small number of items whose content rarely changes.


Working with Command Bars

One of the challenges encountered when creating a Windows CE-based application is to design for a small screen. To maximize the screen space available for applications in the client area, the OS supports a new type of control—the command bar. The command bar is unique to Windows CE because it combines a menu bar, toolbar, and an optional address bar. Windows CE supports multiple command bars, each containing gripper controls that enable users to hide buttons and menus. Command bars can contain combo boxes, edit boxes, and buttons, as well as other types of controls. They also can include the Close (X) button, the Help (?) button, and the OK button.

Command bars vary from 240 pixels through 640 pixels in length depending on the screen resolution. It is recommended that you always display a command bar in Windows CE-based applications.

Command bars are composed of bands, separated by gripper controls. Each band can contain up to one child window, which can be a toolbar or any other control. The default is to display a toolbar. Additionally, each band can have its own bitmap, displayed as a background for the toolbar. A user can resize or reposition a band by dragging its gripper bar. If a band has a text label or icon next to its gripper bar, a user can maximize the band and restore it to its previous size by tapping the label or icon with the pointing device. The following screen shot shows a command bar.

A command bar menu includes a list of commands that drops down when a user taps the menu’s caption on the command bar. Menu titles on a command bar appear in bold text. If you include a menu bar, always position it as the first (leftmost) element on the command bar. If you provide File, Edit, View, Insert, Format, Tools, and Window menus, place them in this order, from left to right. The menu titles appear as bold text surrounded by a rectangular frame.

Windows CE supports ToolTips for command bar and toolbar buttons, but not for menus or combo boxes on a command bar. ToolTips usually display only the title of a button command, but they can also display the shortcut key for the command.

You can place check boxes or radio buttons on the command bar to enable users to toggle between different views. Moving between views can make windows more readable by eliminating unnecessary scrolling. A command bar button can display both text and images. This enables you to include text as part of a button label to provide descriptions, which eliminates the need for ToolTips.

If you choose to place a label next to your edit control on a command bar, you have two choices. You can insert a static text field above or to the left of the control. Alternatively, you can include an edit control label inside the text field as the default text. In this case, you would enclose the label between angle brackets, as in the following example: <name>. Because the user can no longer see the control label when typing text in the field, it is recommended that you use a static text field.

If you provide individual New, Open, Save, and Print buttons on a command bar, you must position them in this order, from left to right. If you provide individual Bold, Italic, and Underline buttons, you must also place them in this order, from left to right. Always make buttons at least 23 x 23 pixels. If you plan to support touch interaction in which users use a finger rather than a stylus, make all buttons at least 38 x 38 pixels. However, to conserve space, consider creating a combo box button instead of three or four separate buttons.


Choosing Controls

Windows CE supplies a set of controls that you can use to build an application. Controls commonly appear in dialog boxes, but can also appear on toolbars and command bars. Windows CE supports many predefined controls, which can be divided into two categories: window controls and common controls. Window controls send the WM_COMMAND message and include buttons, check boxes, radio buttons, push buttons, group boxes, combo boxes, edit controls, list boxes, and static controls. Common controls send the WM_NOTIFY message and include all other controls. They are divided into the following sub-categories: foundation controls, file controls, scale controls, informational controls, and miscellaneous controls used for specific Windows CE-based platform features.

Due to the large number of controls available in Windows CE, determining which control to use in a specified situation can be difficult. When choosing a control, you must consider the type of input you are trying to capture, the abilities and limitations of the control, and the characteristics of your platform’s screen. The following table shows all predefined Windows CE controls and their uses.

Control
Description
Use
Check box A two-part control consisting of a square box and text options. Each option acts as a switch that can be turned on and selected or turned off and deselected. When an item is turned on, a check appears within the square box; otherwise, the square box is empty. A user can select more than one option in a group of check boxes. When setting properties, attributes, or values.

When more than one choice can be selected.

When ample screen space is available.

When options do not change.
Radio button A two-part control consisting of a small circle and text options. When an option is selected, the circle appears highlighted or filled. Only one option can be selected at one time in a group of radio buttons. When setting properties, attributes, or values.

When only one choice can be selected.

When ample screen space is available.

When options do not change.
Push button (Command button) A square or rectangle with a text or graphic label inside. When selected, an application immediately performs the associated action or command. To perform an action.

To display a menu or window.

To set a condition or property value.

When ample screen space is available.
Group box A rectangular frame that surrounds a group of controls. To visually separate and relate groups of controls.

To visually relate elements within a window.
Combo box A control possessing the characteristics of both an edit control and a list box or drop-down list box. Information may either be typed into the edit control field or selected from items displayed in the list box. When options are large in number and not frequently selected.

When the list of options may change.

When only one choice can be selected.

To capture unlisted data.

When users prefer to type information rather than select it from a list.

When a form of input is available.
Drop-down list box A rectangular box with an arrow button on the side. When the arrow button is selected, the box displays a hidden list of items which seems to drop-down from a single item. If the list exceeds the box boundaries, scroll bars appear, enabling a user to view the remaining list. When only one choice can be selected.

When screen space is limited.

When options are many and not frequently selected.
Edit control A rectangular box in which information can be entered by the user or in which information is displayed for read-only purposes. Edit controls typically contain captions and can be designated as either single-line or multiple-line. When options are difficult to categorize and vary in length.

When an input method is available.

When providing a list of options is not feasible.
List box A rectangular box containing a list of items from which either a single selection is made or multiple selections are made. Lists can contain either text or graphics. If the list exceeds the boundaries of the box, scroll bars appear, enabling a user to view the remaining items. When options are large in number and not frequently selected.

When screen space makes radio buttons or check boxes impractical.

When the list of options may change.

When ample screen space is available.
Scroll bar A rectangular container consisting of a scroll area, a slider box, and arrows. Scroll bars are typically found on primary and secondary windows. To view information that uses more than space available.
Static control A text field that displays read-only information. To display a caption.

To provide instructional information.

To display descriptive information.

To display a keyboard hot key for another control.

The following table shows foundation controls, used to contain or manage other controls.

Control
Description
Use
Command band A special kind of rebar control. It has a fixed band at the top containing a toolbar with an optional Close (X) button, OK button, and Help (?) button, in the right corner. By default, each band in the command bands control contains a command bar. You can override this if you want a band to contain some other type of child window. To provide easy access to frequently used commands or options.

When screen space is limited.
Command bar A toolbar that combines a menu bar as well as the Close (X) button, an OK button, and optionally, the Help (?) button. A command bar can contain menus, combo boxes, buttons, and separators. A separator is a blank space you can use to divide other elements into groups or to reserve space in a command bar. To provide easy access to frequently used commands or options.

When screen space is limited.
Toolbar | A panel that contains a set of controls. | To provide easy access to frequently used commands or options.
Property sheet | A control to define property sheets. It accepts dialog box layout specifications and automatically creates tabbed property pages. | When creating property sheets.
Tab control A tab control resembles a divider in a notebook and is used to define sections of information within the same window. To present repetitive, related information.

To present options or settings that can be applied to one object.
Rebar A control that acts as a container for a child window. It contains one or more bands; each band can contain one child window, which can be a toolbar or any other control. Each band can have its own bitmap, which is displayed as a background for the toolbar on that band. A user can resize or reposition a band by dragging its gripper bar. If a band has a text label next to its gripper bar, a user can maximize the band and restore it to its previous size. When screen space is limited.

To hide and show portions of a command bar.

The following table shows file controls, used to display files.

Control
Description
Use
Header control A heading above a column of text or numbers that can be divided into two or more parts for multiple columns. Each part can operate like a command button to support a different function. To display text and graphics.

To aid the user in sorting or sizing columns of information.
Image list A special list box that contains a collection of images that are all the same size, such as bitmaps or icons. Image lists manage images, but do not display them. They are designed to be used with toolbar buttons, list view controls, and tree view controls. When the displaying of icons or images is appropriate.

When you implement a toolbar, treeview control, or listview control.
Tree view A special list box that displays a hierarchical set of labeled items as an indented outline. It includes buttons that enable the outline to be expanded and contracted. To display a relationship between a set of containers.

When ample screen space is available.
List view A special list box that displays a collection of files or folders consisting of an icon and a label. Selection and navigation in this control work similarly to that in a folder window. When the displaying of icons is appropriate.

When ample screen space is available.

The following table shows scale controls, used to increment scaled values.

Control
Description
Use
Spin box An edit control with an associated spin button control. A spin box enables the user to select an option by scrolling through a small list or by typing an item in the edit control field. When options are infrequently selected and small in number.

When screen space is limited.

When there are too many options to display in a combo box or list box.When only one choice can be selected.
Trackbar (Slider) A bar with tick marks on it and a slider or thumb. The tick marks represent a range of values. When a user drags the slider arm, it moves in the appropriate direction, tick by tick. To set an attribute.

When only one choice can be selected.

When a limited range of possible settings exist.

When options are incremented.

When ample screen space is available.

The following table shows informational controls, used to provide information about tools, processes, or time.

Control
Description
Use
Progress bar A display-only control that consists of a rectangular bar that fills from left to right. To provide visual feedback concerning completion of a process.

When ample screen space is available.
Date and time picker A control that provides users with an easy a way to modify date and time information. Each field in the control displays a time element, such as month, day, hour, or minute. To modify date and time information.

When screen space is limited.
Status bar An area within a window, typically at the bottom, that displays information. It can contain display-only controls. To provide information about the current state of what is being viewed in the window.

To provide a descriptive message about a selected menu or toolbar.
Month calendar A child window that displays a monthly calendar. The calendar can display one or more months at a time. To select date information.

When screen space is limited.
ToolTip A small pop-up window containing information about a control. A ToolTip appears when a stylus is held on a control that supports ToolTips. To supply information about a control.

To reduce screen clutter caused by control captions.

The following table shows miscellaneous controls, used for specific Windows CE-based platform features.

Control
Description
Use
HTML viewer | A control that provides the features required to implement the Windows CE Pocket Internet Explorer or Help. | To view HTML text and embedded images.
Rich Ink | A control that captures stylus motions in order to emulate the act of writing or drawing on paper. The control’s document view, under the touch screen, serves as electronic paper. In addition to capturing images, Rich Ink also has editing and formatting capabilities. | To accept user input without using a keyboard.
Voice recorder | A control that provides recording and playback features. | To support voice recording and playback.

In addition to predefined controls, Windows CE supports a new custom draw service. The custom draw service is not a predefined control; it is a service that makes it easy to customize a common control’s appearance. You can use the custom draw service to change a common control’s color or font or to partially or completely draw the control.

In addition to Windows CE controls, you can also create custom controls. When designing custom controls, avoid the following:

  • Controls that are difficult to use

    Make controls easy to use. For example, make controls larger, use colors that contrast with the screen background, and remove nearby controls and unnecessary images. Additionally, when you design a control, have a variety of people test its usability.

  • Controls that are too close together

    Controls should be spaced so that users do not accidentally select one control while intending to select another.

  • Controls that are hard to interpret

    A control should represent its corresponding function. For example, place an image of a scissors on a button control used to “cut” text.

  • Controls that are hard to distinguish

    Controls should have easily recognizable differences. When you have several similar controls close together, users can confuse them. Distinguish controls by using a unique size, position, shape, and contrast for each.

  • Controls that are hidden

    Controls should be obvious. If you want to hide a control, place it where users expect to find it, such as in a menu. Controls that are occasionally unavailable should be disabled and dimmed, not hidden, unless screen space is severely limited.

  • Controls that are not predictable

    Controls that have the same function should operate the same way regardless of where placed. If a control uses a different operating principle, design the control so that it will not be confused with controls that operate differently.


Using Color and Grayscale Palettes

Designers often rely on color to make an application visually pleasing. However, using color randomly or excessively can affect usability. To use color effectively, keep the following guidelines in mind when designing a UI:

  • Display no more than four colors on a single screen at one time and limit the colors for your entire application to fewer than eight.
  • Use color in combination with other emphasis techniques to distinguish areas on the interface and identify important features. Never use color alone to distinguish elements because users may have difficulty distinguishing colors under various lighting conditions. Also use fonts, icons, screen placement, or patterns to distinguish screen elements.
  • Avoid spectrally opposite color combinations, such as red and blue or yellow and purple; they can make images appear blurred.
  • Design applications primarily for a grayscale display. Many users may not have color displays. Then, when the application is complete, add color.
  • Use color contrast for extended viewing; dim colors may not be discernable once a user’s eyes adapt to the color.
  • Avoid colors lacking contrast as well as colors of equal brightness; they are not easily distinguished.
  • Use black, white, and gray to improve resolution.
  • Use common color associations, such as red for stop or green for go, to enhance familiarity.

The color design model for Windows CE uses a 16-color Windows palette, based on the Windows-based color scheme, and is measured in bits per pixel (bpp). Windows CE supports pixel formats of 1, 2, 4, 8, 16, 24, and 32 bpp. Your application should determine the color format supported by a display device, and then adopt a complimentary display strategy.

Note An 8-bpp display driver can display a 32-bpp device-independent bitmap (DIB) by mapping each color in the DIB color table to a specific color on the device. The palette available in the application displaying the bitmap determines what mapping is used. The application can lose color information if it does not use an appropriate palette or if a bitmap uses more colors than the palette can hold.

The following illustration shows a standard 16-color palette.

Some Windows CE-based devices support only a 2 bpp palette, with four gray-scale colors: black, white, light gray, and dark gray. On a grayscale display, a single-pixel graphical element, such as a dot or a line, can be difficult to distinguish without a high contrast color adjacent. For example, white and light gray elements can be hard to see unless presented against a black or dark gray background.

Likewise, light colors may be difficult to distinguish. When using light colors, double the thickness of pixels or lines to strengthen them. Light gray works well for creating a shadow effect around large controls on a white background and for anti-aliasing, which adds colored pixels to a graphic to smooth jagged edges. If you use light gray as a background color for your screen, use a white line to visually separate key areas, such a command bar or owner-drawn menu, from other areas of the screen.

Windows CE does not arbitrate between the palettes of the background and foreground applications. Because of this, you should use only the first ten and last ten colors included in the stock palette of a display device; generally the standard Windows VGA colors.


Creating Icons and Bitmaps

In a graphical user interface, icons convey attributes or tasks. An effective icon clearly represents its function and is easy to remember; an ineffective icon reduces the usability of an application by making it appear obscure and unapproachable.

Icons are used in different ways. They can either resemble what they represent—for example, a book used to represent a dictionary—or they can represent a characteristic of something, such as a gas pump to represent a gas station. Icons can also be symbolic representations, which may or may not be clear to the user. An example of this type of icon is the magnifying glass, which is used on Windows-based desktop platforms to signify a search feature.

Icons are most often used on buttons, but they can be used for progress indicators as well. When a Windows CE color icon has a Windows-based desktop platform application equivalent, both icons use the same design and color. However, you must create a 16-color version and a grayscale version of the icon to ensure that it displays correctly on both color and 2-bpp devices.

Note The icon editor in the Windows CE Toolkit for Visual C++ version 5.0 can create icon (.ico) files that retain both 16-color and 2 bpp gray versions of an icon.

In addition to using Windows-based desktop platform application icon equivalents, you can create your own icons using the standard Windows-based 16-color palette. To add depth to an icon, use highlights and shadows, but remember, the icons you create must translate correctly to 2 bpp gray if the target device supports both grayscale and color displays. The following table shows how the 16-color palette translates to four grays.

Color
Red
Green
Blue
Gray conversion
Black | 0 | 0 | 0 | Black
White | 255 | 255 | 255 | White
Dark gray | 128 | 128 | 128 | Dark gray
Light gray | 192 | 192 | 192 | Light gray
Dark red | 128 | 0 | 0 | Black
Red | 255 | 0 | 0 | Dark gray
Dark yellow | 128 | 128 | 0 | Dark gray
Yellow | 255 | 255 | 0 | Light gray
Dark green | 0 | 128 | 0 | Black
Green | 0 | 255 | 0 | Dark gray
Dark cyan | 0 | 128 | 128 | Dark gray
Cyan | 0 | 255 | 255 | Light gray
Dark blue | 0 | 0 | 128 | Black
Blue | 0 | 0 | 255 | Dark gray
Dark magenta | 128 | 0 | 128 | Dark gray
Magenta | 255 | 0 | 255 | Light gray


User Input Devices

Input devices enable users to interact with the user interface. Windows CE supports several types of user input devices, such as a keyboard, a mouse, a touch screen, a stylus, and voice recognition, though input devices available on a specific device may vary. For general design considerations for user input devices, see Windows Interface Guidelines for Software Design.


Providing User Feedback

In addition to receiving user input, a user interface provides feedback by displaying messages. Messages are communications to the user displayed on the screen. They inform the user of system status, or prompt the user to complete some action. Effective messages are clear and concise.

When you include an identification number in message text, place it at the end of the message text and not in the title bar or at the beginning of the text where it may decrease the user’s ability to quickly read the message.


Using Communications

Windows CE supports a wide range of communications options for transmitting and receiving data. You can use communications for the following tasks:

  • Downloading files from a desktop computer or network
  • Exchanging information with another Windows CE-based device
  • Sending and receiving e-mail
  • Sending data to a server
  • Browsing the Internet
  • Reading bar codes

To support the many different types of communication, Windows CE-based devices can include a variety of hardware. Some hardware may be an integral part of the device. For example, many Windows CE-based devices include a connector for a serial cable or an infrared (IR) transceiver. If a hardware expansion slot is available, users can also extend the built-in capabilities of the device with third-party communications hardware, such as a modem or a bar code reader. Windows CE-based devices include the following communications hardware:

  • Serial cables
  • IR transceivers
  • Wireless transceivers
  • Modems
  • Bar code readers

Windows CE supports two basic types of communications technology: serial and network. While some hardware can support only one type, the same hardware is often used for both. Which type of communication is appropriate is governed in large part by how the communicating devices are connected.

You can use serial communications when two devices have a one-to-one connection. Each sender has only one possible receiver, and vice versa. A common example is two devices connected by a serial cable. Because there is no ambiguity about where the data is from and where it is going, it can be streamed from one device to the other with little or no processing. Serial communications include transferring information from a desktop computer to a Windows CE-based device by means of a serial cable, and sending text to a printer using an IR transceiver.

With networks, every transmission is usually seen by many receivers, whether or not they are the intended recipient. For one-to-one communication to take place over a network, simply streaming the information will not work. Each transmission must also include addresses that identify the sender and the intended recipient. A receiver can thus monitor the network and pick out only those transmissions that are addressed to it. It can then use the senders address to respond. The Windows CE network stack handles addressing and related tasks. You use a network for the following tasks:

  • Downloading a file from a corporate local area network to a Windows CE-based device
  • Browsing the Internet using a modem connection to an independent service provider
  • Sending e-mail when you are away from home using a wireless service


Serial Communications

Serial communications requires a one-to-one connection between transmitter and receiver, typically by way of a serial cable. IR transceiver modems are also used for serial communications.

From the standpoint of software, each serial device is identified by its COM port name, for example, “COM1:” and “COM2:.” The COM-port assignments are stored in the registry under \HKEY_LOCAL_MACHINE\Drivers. Because they may be installable, check \HKEY_LOCAL_MACHINE\Active to see which drivers are loaded.

Serial communication over a COM port is similar to reading from, or writing to, a file, and it uses some of the same functions. Regardless of the hardware, the basic procedure works as described in the next section, “Implementing Serial Communications.”

Using IR transceivers is more complex. Windows CE supports two ways to use an IR transceiver for serial communications. One approach supported by some Windows CE-based devices treats the IR transceiver like a serial cable. The data is not processed by the system in any way. The sending and receiving applications are responsible for dealing with collision-detection and other potential problems. This approach is referred to as raw infrared, or raw IR.

The COM port assigned to raw IR is determined by the original equipment manufacturer (OEM) and is listed in the registry. Because it may share a port assignment with a wired serial connector, you should check the registry. If the port is shared, you must use EscapeCommFunction to set the port to IR mode.

A second approach to serial IR communications uses the Infrared Data Association (IrDA) protocols. These protocols are part of the network stack, and are discussed in “Infrared Sockets” later in this chapter. To simplify their use for serial communications, Windows CE provides an emulator (IrComm), that enables an application to communicate using the IrDA protocols in much the same way it does with raw IR.

From a programming standpoint, the main difference between raw IR and IrComm is that they have different COM-port assignments. With IrComm, there is also no need to explicitly configure the port for IR by calling EscapeCommFunction.


Implementing Serial Communications

This procedure outlines how to implement serial communications in an application. With the exception of step 3, the procedure is identical for all three approaches.

To use serial communications

  1. Determine which COM port you need to open.

    Port numbers are stored in the \HKEY_LOCAL_MACHINE\Drivers registry key. Active drivers are listed in the \HKEY_LOCAL_MACHINE\Active registry key.

  2. Call CreateFile with lpFileName set to the COM-port name, for example,”COM1:.”

    The colon (:) is part of the port name and must be included. Set the lpSecurityAttributes parameter to NULL and the dwFlagsAndAttributes parameter to zero.

  3. For raw IR transmission, place the port in IR mode by calling EscapeCommFunction with hFile set to the handle returned by CreateFile and dwFunc set to SETIR.
  4. Call SetCommTimeouts to set the communication timeouts.
  5. Call the ReadFile and WriteFile functions to transmit and receive serial data.

    When one thread is waiting for a ReadFile function to return, ReadFile calls issued by other threads are blocked until the initial ReadFile call returns. The same is true for the WriteFile function.

  6. Call CloseHandle to close the serial port.


Using a Modem

An application that uses a modem must be able to handle such tasks as dialing the appropriate phone number and breaking the connection when the session is over. To simplify the process of using a modem, Windows CE supports a subset of the Telephony API (TAPI), which handles only outbound calls. TAPI provides a set of functions that applications can use to handle the process of making and managing a modem connection, but not the actual transfer of data.

To make a modem connection using TAPI

  1. Call lineInitialize to initialize TAPI.

    This function returns the number of line devices available. You must provide the name of the callback function that TAPI should use to return data. See TAPI Callback Function.

  2. Call lineOpen to open the line.
  3. Call lineMakeCall.

    When the call is set up, TAPI returns a LINE_REPLY message through the callback function. This message indicates only that the call has been established at the local end, perhaps indicated by a dial tone.

    As the connection process proceeds, TAPI returns a series of LINE_CALLSTATE messages through the callback function indicating the progress of the connection, for example, dialtone and ringing. When the connection is completed, TAPI returns a LINECALLSTATE_CONNECTED message.

    During information transfer, TAPI continues to manage the connection, but the application handles data transmission and reception. When transmission is finished, TAPI returns a LINE_CALLSTATE message, such as one indicating that a remote disconnect has occurred.

  4. Call lineClose to close the line.
  5. Call lineShutdown to terminate the session.


TAPI Callback Function

TAPI sends messages to an application through a callback function implemented by the application. Implement the callback function according to the following definition.

void CALLBACK LineCallbackFunc (DWORD hDevice, DWORD dwMsg, DWORD wCallbackInstance, DWORD dwParam1, DWORD dwParam2, DWORD dwParam3);
hDevice
A handle to the line device associated with the callback. Do not use the HANDLE type for this parameter.
dwMsg
The line device message. The following table shows the line device messages.
Message
Description
LINE_ADDRESSSTATE | Indicates that the status of an address on a currently open line has changed
LINE_CALLINFO | Indicates that call information has changed
LINE_CALLSTATE | Indicates that the status of the call has changed
LINE_CLOSE | Indicates that the line device has been forcibly closed
LINE_CREATE | Indicates that a new line device has been created
LINE_DEVSPECIFIC | Indicates that a device-specific event has occurred
LINE_LINEDEVSTATE | Indicates that the state of a line device has changed
LINE_REMOVE | Indicates that a device has been removed, usually for good
LINE_REPLY | Reports the results of function calls that completed asynchronously
LINE_REQUEST | Reports the arrival of a new request from another application

dwCallbackInstance
The callback instance data.
dwParam1
A message parameter, used as needed to send additional information.
dwParam2
A message parameter, used as needed to send additional information.
dwParam3
A message parameter, used as needed to send additional information.


Windows CE Networking

Windows CE supports a variety of networking options that range from serial link networking over a modem to wireless communications. Windows CE supports the following networking capabilities:

  • Sending an Internet Control Message Protocol (ICMP) request, also known as a ping
  • Communicating over the Internet with the Windows CE Internet API (WinInet)
  • Accessing remote file systems
  • Using Windows Sockets (Winsock)
  • Using network security features
  • Accessing an IR transceiver using the IrDA protocols

Windows CE supports the following types of networking:

  • Local area networking
  • Wired serial-link networking using serial cables or modems
  • IR networking
  • Wireless networking

Windows CE network support is organized in layers. The network stack is responsible for taking data from applications, breaking it into one or more packets, and adding whatever header information is necessary to ensure that the packet arrives at its destination. The following illustration shows the schematics of the network stack.

Network stack schematics

There are several ways to access the network stack:

  • The Winsock API provides applications with the means to exchange packets with a remote site. It handles all the details of creating the needed header information, but leaves the format of the data up to the application. All network communication on Windows CE uses Winsock directly or indirectly.
  • WinInet supports high-level data protocols, such as Hypertext Transfer Protocol (HTTP) or File Transfer Protocol (FTP). These APIs ensure that the data is properly structured and relieve applications of the need to use Winsock directly.
  • A Common Internet File System (CIFS) redirector gives applications access to remote file systems. Windows CE also provides support for a Remote Access Service (RAS) client, which allows a Windows CE-based device to connect to a remote host.

The following sections describe the Windows CE network stack from the top to the bottom.


Sending an ICMP Request

Send an ICMP request, or ping, to determine whether or not a particular host is available.

To send an ICMP request

  1. Call ICMPCreateFile to create a handle on which requests can be issued.
  2. Call ICMPSendEcho to send an ICMP echo request. It returns the status of the host.
  3. Call ICMPCloseHandle to close the handle created by ICMPCreateFile.


Communicating over the Internet

As previously mentioned, Windows CE supports WinInet, the Windows CE Internet API. WinInet is used by applications that use HTTP and FTP Internet protocols. The WinInet.dll module exports the WinInet functions, which you can use to develop Internet client applications, such as Web browsers and file-transfer applications.

Although you can use Winsock connections to access HTTP and FTP, applications that use WinInet do not have to identify the underlying protocols. The functions remain constant even when Internet protocols change.

On Windows CE-based platforms, most WinInet callback functions operate synchronously and support Unicode input by default. Only InternetReadFile and InternetQueryDataAvailable operate in both synchronous and asynchronous modes. To request asynchronous operation on these two functions, an application sets the INTERNET_FLAG_ASYNC flag when it calls InternetOpen, which is the initial function required to establish an Internet connection.

WinInet allows multithreaded applications to make concurrent calls to WinInet functions from different threads. Although the functions in WinInet complete any necessary synchronization, they do not validate parameters.


Establishing an Internet Connection

WinInet handles are maintained in a hierarchy in which you must call a higher-level function before you call a dependent function. Handles returned by the WinInet functions in Windows CE control only Internet functions. They are not native system handles. Therefore, only an Internet function works with its corresponding Internet handle. Win32 routines, such as ReadFile or CloseHandle, fail.

To establish an Internet connection with WinInet

  1. Call InternetOpen.

    This function initializes an Internet handle that is used by all subsequent functions.

  2. Call InternetConnect.

    This function uses the handle returned by InternetOpen to create an HTTP or FTP session.

  3. Call any of the other WinInet functions, such as HttpOpenRequest or FtpFindFirstFile.
  4. Call InternetCloseHandle to terminate Internet operations.

    This function closes the Internet handle and frees any resources associated with it. InternetCloseHandle terminates existing activity on the handle and discards remaining data. If InternetCloseHandle closes a parent handle, all of the parent’s child handles are also closed. If an operation requires more than one Internet handle, you may need to make multiple calls to InternetCloseHandle.


Accessing the HTTP Protocol

Applications must interact with the HTTP protocol to access resources on the Internet. You can use the HTTP functions provided by WinInet to access the HTTP protocol, or you can use the InternetOpenUrl function to access a Uniform Resource Locator (URL) directly.

To use the HTTP functions provided by WinInet

  1. Call InternetOpen to initialize an Internet handle.
  2. Call InternetConnect to create an HTTP session.
  3. Call HttpOpenRequest to create an HTTP request handle.
  4. Call HttpSendRequest to send an HTTP request.

    You can use the same HTTP request handle generated in the previous step in multiple calls to HttpSendRequest, but the application must read all data returned from the previous call before calling the function again.

  5. Call InternetReadFile to download information.

    – Or –

    Call InternetQueryDataAvailable to query how much data is available to be read by a subsequent call to InternetReadFile.

  6. Call InternetCloseHandle to terminate Internet operations.

Use the InternetOpenUrl function to parse the URL string, establish an Internet connection, and prepare for data download. This is practical for applications that are not concerned with protocol details, but are interested in retrieving only data that relates to a specific URL.

To use InternetOpenUrl to access a URL

  1. Call InternetOpen to initialize an Internet handle.
  2. Call InternetOpenUrl to open a connection to the URL using the handle created by InternetOpen.

    InternetOpenUrl returns a handle that subsequent functions can use, such as InternetReadFile or InternetSetFilePointer. You do not need to call InternetConnect when you use InternetOpenUrl.

  3. Call InternetReadFile to download information.

    – Or –

    Call InternetFindNextFile to navigate between directories.

    InternetQueryDataAvailable can use the handle returned by InternetOpenUrl to query how much data is available to be read by a subsequent call to InternetReadFile.

  4. Call InternetCloseHandle to close the handle created by InternetOpenUrl.
  5. Call InternetCloseHandle again to terminate Internet operations.


Accessing an FTP Server

Applications use WinInet to perform the following tasks on an FTP server:

  • Navigating between directories
  • Enumerating, creating, removing, and renaming directories
  • Renaming, uploading, downloading, and deleting files

To access an FTP server with WinInet

  1. Call InternetOpen to initialize an Internet handle.
  2. Call InternetConnect to create an FTP session.

    InternetConnect returns a handle that subsequent functions can use, such as FtpFindFirstFile or FtpGetFile.

  3. Call InternetCloseHandle to terminate the FTP session.
  4. Call InternetCloseHandle again to terminate Internet operations.

The following table shows additional WinInet functions you can use on an FTP server.

Function
Description
FtpGetCurrentDirectory | Determines which directory the application is in
FtpSetCurrentDirectory | Allows the application to change to a different directory
FtpCreateDirectory | Creates a new directory
FtpFindFirstFile | Searches for a specific file
InternetFindNextFile | Finds the next file using the handle returned from FtpFindFirstFile
FtpRenameFile | Renames a file or directory
FtpDeleteFile | Deletes a file
FtpPutFile | Uploads a file to an FTP server
FtpGetFile | Downloads a file from an FTP server

Note Applications must specify a directory name relative to the current directory or include the full directory path.


Windows Sockets

Sockets are a general purpose, connection-oriented networking interface supported by most operating systems. The Windows implementation of sockets, commonly called Winsock, is designed to run efficiently on Windows while maintaining compatibility with the Berkeley Software Distribution standard, known as Berkeley Sockets. The Winsock API is the only way for an application to access the Transmission Control Protocol/Internet Protocol (TCP/IP) or IrDA protocols on a Windows CE-based device. High-level APIs, such as WinInet, use Winsock internally, but it can also be used directly.

Windows CE supports all of the standard Winsock 1.0 calls. It also implements WSAIoctl, which is provided to allow applications to set and query secure socket options.

Each socket that is created has an associated SOCKADDR structure that identifies the underlying transport protocol. Because the length of a network address is protocol-dependent, each supported protocol has its own SOCKADDR structure. The Windows CE implementation of Winsock supports two transport protocols, TCP/IP and IrDA. Their associated SOCKADDR structures are SOCKADDR_IN for TCP/IP protocol and SOCKADDR_IRDA for IrDA protocol.

For more information on Winsock, see the Microsoft Platform SDK.


Infrared Sockets

Winsock is typically used with the TCP/IP protocols. Infrared Sockets (IrSock), is an extension to Winsock that allows it to be used also for IR communication using the IrDA protocol. Each endpoint must support an IrDA-compliant device and an IrDA-compliant protocol stack.

Some Winsock functions work differently with IrDA than they do with TCP/IP. The following list describes the principal differences:

  • Name service

    Conventional Winsock name service is best suited to fixed networks in which the group of devices that can accept a socket connection is relatively static. Conversely, IrDA is designed to handle browsing for whatever resources are within range. It works in an ad hoc manner, and devices come and go frequently as they move in and out of range.

    Because of these differences, IrSock does not use the conventional Winsock name service functions. Instead, name service is incorporated into the communication stream.

  • Method of addressing

    Addressing is based on Logical Service Access Point Selectors (LSAP-SELs), numbered from 1 through 127. Because of the small range of values available, it is usually better not to bind sockets directly to an LSAP-SEL. Instead, the Information Access Service (IAS) provides a means for dynamic binding of sockets to LSAP-SELs.

    To use IAS, a server application binds a socket to an IAS service name. The client application uses the service name when using Connect. Neither application knows, or needs to know, the LSAP-SEL that is assigned by the IAS. This procedure is outlined in the following sections.

  • Enhanced socket options

    Windows CE includes two socket options to access the unique features of the IrDA protocol:

    • IRLMP_IAS_SET allows an application to set a single class in the local IAS. The application specifies the class to set, the attribute, and the attribute type. The application must allocate a buffer of the necessary size for the passed parameters.
    • IRLMP_RAW_MODE allows an application to switch between the reliable TinyTP mode, which is the default setting, and the less reliable IrLMP mode. This option is only available after calling socket to obtain a socket handle and before calling bind and connect.


Using Infrared Sockets

The basic procedure for using IrSock is similar to that for Winsock. Server applications and client applications have somewhat different procedures.

To create and use a socket with a server application

  1. Allocate a stream socket with socket. Use AF_IRDA for the address format parameter and SOCK_STREAM for the type.
  2. Bind the service name to the socket with bind. Pass a SOCKADDR_IRDA structure for the address parameter.
  3. Listen for an incoming connection with listen.
  4. Accept an incoming client with accept.
  5. Use send and recv to communicate with the client.
  6. Close the socket with closesocket.

To create and use a socket with a client application

  1. Allocate a stream socket with socket, as with the server application.
  2. Search for the server, and retrieve its identifier with getsockopt.
  3. Connect to the server with connect, using SOCKADDR_IRDA for the name parameter.
  4. Use send and recv to communicate with the server.
  5. Close the socket with closesocket.

The code examples in the following two sections demonstrate these procedures, using IAS. You could run these samples on a pair of Handheld PCs (H/PCs) or Palm-size PCs, for example.


Sample Infrared Socket Server

This sample IrSock server allocates a socket and binds it to the IAS name, “MyServer.” It then allocates a single connection object and prepares the server to listen for incoming connections. When the client contacts the server, the server accepts the connection. It then receives a string from the client, passes one back, and closes the connection.

#include <windows.h>
#include <af_irda.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                  LPTSTR lpCmdLine, int nCmdShow )
{
   SOCKET         ServerSock,
                  ClientSock;

   SOCKADDR_IRDA  address = {AF_IRDA, 0, 0, 0, 0, "MyServer"};

   char           helloServer[25];               // ASCII String
   TCHAR          helloText[25];                 // UNICODE String
   int            idx = 0;

   ServerSock = socket(AF_IRDA, SOCK_STREAM, 0);

   bind(ServerSock, (struct sockaddr *)&address, sizeof(address));

   listen(ServerSock, 1);

   ClientSock = accept(ServerSock, 0, 0);

   recv(ClientSock, helloServer, sizeof(helloServer), 0);

   for (idx = 0; idx <= sizeof(helloServer); idx++)
         helloText[idx] = helloServer[idx];

   MessageBox (NULL, helloText, TEXT("IR Server"), MB_OK);

   send(ClientSock, "Hello Client!", strlen("Hello Client!")+1, 0);

   closesocket(ClientSock);
   closesocket(ServerSock);

return 0;
}


Sample Infrared Socket Client

This sample IrSock client opens a socket and makes five attempts to locate a server. If none is found, it displays a message box to inform the user of the failure. When a server is detected, the client queries the server for its device identifier and sends a greeting to the service named “My Server.” It then waits for the server to respond, displays a message box with the response, and closes the socket.

#include <windows.h>
#include <af_irda.h>

#define    NumRetries    5

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                  LPTSTR lpCmdLine, int nCmdShow )
{
   SOCKET         sock;
   SOCKADDR_IRDA  address = {AF_IRDA, 0,0,0,0, "MyServer"};
    EVICELIST     devList;
   int            devListLen = sizeof(devList),
                  cnt = 0,
                  idx = 0;
   char           helloClient[25];
   TCHAR         helloText[25];

   sock = socket(AF_IRDA, SOCK_STREAM, 0);
   devList.numDevice = 0;      // initialize number of devices to zero

   while ((devList.numDevice == 0) && (cnt <= NumRetries))
   {
         getsockopt(sock, SOL_IRLMP, IRLMP_ENUMDEVICES,
                  (char *)&devList, &devListLen);
         cnt++;
         Sleep(1000);          // Wait one second before retrying
   }
   if  (cnt > NumRetries)
   {
         MessageBox (NULL, TEXT("Server could not be located"),
                   TEXT("IR Client"), MB_OK);
   }
   else
    {
                               // Get socket address of server
         for (idx = 0; idx <= 3; idx++)
            address.irdaDeviceID[idx] =
            devList.Device[0].irdaDeviceID[idx];

         connect(sock, (struct sockaddr *)&address,
                     sizeof(SOCKADDR_IRDA));

        send(sock, "Hello Server!", strlen("Hello Server!")+1, 0);

        recv(sock, helloClient, sizeof(helloClient), 0);

        for (idx = 0; idx <= sizeof(helloClient); idx++)
             helloText[idx] = helloClient[idx];

        MessageBox (NULL, helloText, TEXT("IR Client"), MB_OK);

        closesocket(sock);
   }
return 0;
}


Network Security Features

Windows CE supports Program Comprehension Tool (PCT) 1.0 and Secure Sockets Layer (SSL) versions 2.0 and 3.0 security protocols. These protocols are available either through WinInet or directly from Winsock.

The simplest approach to using the security protocols is to use WinInet.

To access security protocols with WinInet

  1. Call InternetOpen to get an Internet handle.
  2. Connect with InternetConnect, using INTERNET_DEFAULT_HTTPS_PORT as the nServerPort parameter.
  3. For HTTPS, invoke HttpOpenRequest, with the INTERNET_FLAG_SECURE flag set.
  4. Proceed with the remainder of the session.

    For information, see Accessing the HTTP Protocol earlier in this chapter.


Certificate Authentication

Authentication is the process of determining whether or not a remote host can be trusted. To establish its trustworthiness, the remote host must provide an acceptable authentication certificate based on public-key cryptography. Windows CE supports X.509-style certificates.

Remote hosts establish their trustworthiness by obtaining a certificate from a Certification Authority (CA). The CA may, in turn, have certification from a higher authority, and so on, creating a chain of trust. To determine whether a certificate is trustworthy, an application must determine the identity of the root CA, and then decide if it can be trusted.

Windows CE maintains a database of trusted CAs. When a secure connection is attempted by an application, Windows CE extracts the root certificate from the certification chain and checks it against the CA database. It delivers the root certificate to the application through a certificate validation callback function, along with the results of the comparison against the CA database.

Applications bear ultimate responsibility for deciding whether or not the certificate is acceptable. They are free to accept or reject any certificate, based on whatever criteria are appropriate. If the certificate is rejected, the connection is not completed. At a minimum, a certificate should meet the following two requirements: It should be current, and the identity contained within the certificate should match the identity of the root CA.

The certificate validation callback function must be implemented by all client applications that use secure sockets. The value it returns determines whether or not the connection will be completed by Winsock. It must have the following syntax:

int SslValidate (
         DWORD      dwType
         LPVOID     pvArg
         DWORD      dwChainLen
         LPBLOB     pCertChain
         DWORD      dwFlags
);

The parameters contain the following information:

  • The dwType parameter specifies the type of data pointed to by pCertChain. This must be SSL_CERT_X.509, specifying that pCertChain is a pointer to an X509 style certificate.
  • The pvArg parameter is the application-defined context, passed by the SSLVALIDATECERTHOOK structure.
  • The dwChainLen parameter is the number of certificates pointed to by pCertChain. It will always be equal to one.
  • The pCertChain parameter is a pointer to the root certificate.
  • If the root issuer of the certificate could not be found in the CA database, the dwFlags parameter will contain SSL_CERT_FLAG_ISSUER_UNKNOWN. The application can either attempt to verify the issuer itself, or return SSL_ERR_CERT_UNKNOWN.

The following table shows the values that are returned by the callback function.

Return value
Description
SSL_ERR_BAD_DATA | The certificate is not properly formatted.
SSL_ERR_BAD_SIG | The signature check failed.
SSL_ERR_CERT_EXPIRED | The certificate has expired.
SSL_ERR_CERT_REVOKED | The certificate has been revoked by its issuer.
SSL_ERR_CERT_UNKNOWN | The issuer is unknown, or some unspecified problem arose in the processing of the certificate, rendering it unacceptable
SSL_ERR_OKAY | The certificate is acceptable.


Implementing a Secure Socket

The following procedure describes how to establish a secure socket connection.

To implement a secure socket

  1. Create a socket with socket.
  2. Set the socket in secure mode with setsockopt. Set the level parameter to SO_SOCKET, optname to SO_SECURE, and set optval to a DWORD set to SO_SEC_SSL.
  3. Specify the certificate validation callback function by invoking WSAIoctl with the SO_SSL_SET_VALIDATE_CERT_HOOK control code.
  4. To specify a particular security protocol, invoke WSAIoctl with the SO_SSL_GET_PROTOCOLS control code to determine the default protocols. Then call WSAIoctl with the SO_SSL_SET_PROTOCOLS control code to select the protocols to be enabled. Otherwise, Windows CE will select the protocol to be used.
  5. Make a connection with connect.

    The certificate callback function is automatically invoked. The connection can be completed only if the callback function verifies the acceptability of the certificate by returning SSL_ERR_OKAY.

  6. Transmit and send as usual.

    The send and recv functions automatically encrypt and decrypt the data.

  7. When finished, close the socket with closesocket.


Using a Deferred Handshake

A deferred handshake allows an application to create an unsecured connection and then later convert it to a secure connection.

To implement secure sockets with a deferred handshake

  1. Create a socket with socket.
  2. Set the socket in secure mode with setsockopt.

    The level parameter should be set to SO_SOCKET, optname should be set to SO_SECURE, and optval should be a DWORD set to SO_SEC_SSL.

  3. Specify the certificate validation callback function by invoking WSAIoctl with the SO_SSL_SET_VALIDATE_CERT_HOOK control code.
  4. Set the socket in deferred handshake mode with WSAIoctl. The control code should be set to SO_SSL_SET_FLAGS and the flag set to SSL_FLAG_DEFER_HANDSHAKE.
  5. Establish a non-secure connection with the remote party using connect.
  6. Transmit and receive unencrypted data as usual.
  7. To switch to secure mode, invoke WSAIoctl with the SO_SSL_PERFORM_HANDSHAKE control code.

    The certificate callback function is automatically invoked. The handshake is successful only if the callback function verifies the acceptability of the certificate by returning SSL_ERR_OKAY.

  8. Transmit and receive as usual.

    The send and recv functions encrypt and decrypt the data automatically.

  9. Close the socket with closesocket when you are finished.


Providing Secure Communications

In addition to the capabilities provided through WinInet and Winsock for creating a secure socket connection, Windows CE supports the Microsoft Cryptographic API (CryptoAPI) and Security Support Provider Interface (SSPI) for secure communications. The following illustration shows the relationship between these elements and your applications.

Security components

The cryptographic base functions supported in Windows CE already exist as an integral part of CryptoAPI. The services provided by these functions enable you to add cryptography to your Windows CE applications without requiring extensive knowledge of cryptographic processes.

The algorithms and standards used by the CryptoAPI are implemented through cryptographic service providers (CSPs). One CSP, the Microsoft Base Cryptographic Provider 1.0, is included with Windows CE through the rsabase.dll module. The CryptoAPI functions are available through the Coredll.dll module.

SSPI provides a common interface between transport-level applications and security providers. It provides a mechanism by which a transport application can call one of several security providers and obtain an authenticated connection without knowing the details of the security protocol. Two security providers are included with Windows CE: The NT LAN Manager (NTLM) and SSL2.0, SSL3.0, and PCT 1.0 that are provided through the Schannel security service. The Schannel security service is accessed through Winsock. The SSPI functions are available through the Secur32.dll module.

For more information about the CryptoAPI and Security SPI features available for Windows CE, see Cryptography and Security Support Provider Interface.


TCP/IP

TCP/IP stacks are designed to work efficiently on wired networks. They may perform differently on wireless networks. For example, settings that are appropriate to a 10 Mbps Ethernet connection may consume more bandwidth than necessary on a wireless network by generating unneeded retransmission requests.

To use wireless networking efficiently, some TCP/IP parameters may need to be tuned to the characteristics of the supporting network. Because network parameters are maintained on a per-adapter basis, applications must determine the appropriate adapter and change the associated registry settings. For more information about modifying the registry, see Accessing Persistent Storage. The following list shows the parameters most likely to need modification:

  • Receive window size. The registry key for this parameter is <Adapter Name>\tcpip\parms\TcpWindowSize. In general, larger receive windows work better with high-delay, high-bandwidth networks. For greatest efficiency, the receive window should be an even multiple of the TCP Maximum Segment Size (MSS). It should not exceed the system maximum. The registry key for this parameter is tcpip\parms\GlobalMaxTcpWindowSize.
  • Initial roundtrip time. The registry key for this parameter is <Adapter Name>\tcpip\parms\TcpInitialRTT. Roundtrip times are generally longer for wireless networks than for wired networks.
  • Delayed acknowledgment timer. The registry key for this parameter is <Adapter Name>\tcpip\parms\TcpDelAckTicks.


Data Link Protocols

Windows CE provides data-link layer support for both serial input/output (I/O) and local area networks (LANS). Windows CE supports the following protocols:

  • Point-to-Point Protocol (PPP) and serial line Internet protocol (SLIP) for serial- and modem-based networking.
  • Dynamic Host Configuration Protocol (DHCP) and Address Resolution Protocol (ARP) for LANs.
  • A subset of NDIS 4.0:
    • Only Ethernets are supported.
    • Only miniport drivers are supported, not intermediate or legacy drivers.

NDIS 4.0 does not expose an API to applications. For information related to device drivers, see the documentation for the Windows CE DDK.

NDIS 4.0 for Windows CE is packaged as a DLL, rather than a .sys file. This feature permits the ARP and NDIS modules to be partially installed. If an OEM chooses this option, the network stack will be configured for them but the DLLs will not be added to ROM. If the modules are needed for an application, the DLLs can be added to RAM.


Remote Access Service

RAS is a software-based multiprotocol router that is used to connect a remote device, known as a RAS client, to a host desktop computer, known as an RAS server. RAS applications are usually executed on the client device and connect to the server using PPP/SLIP.

Windows CE provides support for an RAS client. While most of the standard Win32 RAS functions are supported, only one point-to-point connection at a time is allowed. The connection can be a wired serial connection or a dial-up modem connection.

Entries in the RAS phone book contain the information necessary to establish an RAS connection. Windows CE stores these entries in the registry. The RAS phone book includes the following information:

  • The phone number to dial, along with country code and area code.
  • The IP addresses to use while the connection is active.
  • The network protocols.
  • The type of device being used to make the connection.

Windows CE-based applications that use RAS while running under emulation can link to Coredll.lib to resolve the RAS API entry points. This is the proper method for device builds. Alternatively, applications can use the NT RAS APIs by linking to NT Rasapi32.lib. The NT Remote Access Service needs to be installed on the desktop computer with at least one port configured for dial out. A modem is also required to use RAS.


Using RAS

This procedure outlines how to connect with a RAS server.

To use RAS

  1. Determine which phone number to call.

    If the number is in the phone book, you can retrieve it with RasEnumEntries.

  2. Establish a connection with RasDial.
    • Ignore the dialExtensions parameter, and set it to NULL.
    • Set the lpszPhoneBook parameter to NULL.

      Phone book entries are stored in the registry.

    • Set the dwNotifierType parameter to 0xFFFFFFFF.

      If the application needs to receive messages from RAS, the messages must be sent to an HWND. There is no support for callback functions.

  3. When the session is complete, terminate it with RasHangup.

If an HWND was specified in the RasDial call, it receives a WM_RASDIALEVENT message every time a change-of-state event occurs. The wParam and lParam values carry the following information:

  • wParam: RASCONNSTATE indicates the state that the RasDial remote access connection process is about to enter.
  • lParam: A non-zero value for dwError indicates which error has occurred.


Cryptography

Cryptography provides a way to distribute files and messages in secret code, or cipher, so they can only be read by intended recipients. Cryptography maintains secrecy and ensures data integrity to achieve secure communications in your applications, even with nonsecure transmission media, such as the Internet.

This chapter will cover the following aspects of cryptography:

  • Encryption and decryption
  • The Windows CE implementation of the Microsoft cryptographic system
  • How to use the Microsoft cryptographic application programming interface (CryptoAPI)


Encryption and Decryption

Encryption is the process of transforming data into a form unreadable by anyone without a secret key. Decryption is the reverse process in which the encrypted data is converted back to its original form. When a user encrypts a sensitive file, an intruder cannot read the file without the proper key to decrypt it. Adding a digital signature , a form of arithmetical thumbprinting,ensures that the original message has not been tampered with.

Data that has not been encrypted is known as plaintext. To encrypt plaintext, an encryption key is used to impose an encryption algorithm onto the data. The resulting encrypted message is known as ciphertext. To return the ciphertext to plaintext, a user must possess the appropriate decryption key. A decryption key consists of random numbers, approximately 40 to 2000 bits in length, The key imposes a decryption algorithm onto the data. This decryption algorithm reverses the encryption algorithm that was previously imposed, thus returning the data to its original form. The longer the key, the more difficult it is to decrypt ciphertext without possessing the key. For a key size of just 40 bits, there are 240 possible keys, or just over one trillion.

There are two primary encryption approaches: symmetric and public-key. Symmetric encryption is the most common type of encryption and uses the same key for encryption as decryption. This key is known as a session key. Public-key encryption uses two different keys, a public key and a private key, where one key encrypts the message and the other decrypts it.The public key is widely distributed while the private key is kept secret.

Aside from key length and the encryption approach, many other factors and variables have an impact on the success of cryptographic systems. For instance, different cipher modes can be used to vary the encryption along with initialization vectors and salt values. Cipher modes define the method in which data is encrypted. The stream cipher mode encrypts data one bit at a time. The block cipher mode encrypts data one block at a time. Although they tend to execute more slowly than stream ciphers, block ciphers are generally more secure. Within block ciphers, there are four modes of encryption: electronic cookbook (ECB), cipher block chaining (CBC), cipher feedback mode (CFB), and output feedback mode (OFB). For information on these modes, see Encrypting and Decrypting Data.

Initialization vectors are random numbers used as starting points when encrypting data. Usually, initialization vectors have the same number of bits as the block size and do not require encryption. With initialization vectors, two identical plaintext messages can be encrypted with the same key and result in two completely different ciphertext messages. This variation is done by encrypting each plaintext message with a different initialization vector.

Salt values consist of random numbers and can be transmitted in plaintext form. Two identical packets of plaintext can be encrypted into two completely different packets of ciphertext, if the salt value is changed with each packet sent.

In addition to encrypting the data, a user can digitally sign data to allow another user to verify that the data has not been changed since it was signed. The identity of the user that signed the data can also be verified. This digital signature consists of a small amount of binary data, typically less than 256 bytes. A digital signature can be bundled with signed messages or stored separately; depending on the application.


The Microsoft Cryptographic System

The Microsoft cryptographic system is composed of different components. The three executable portions are the application, the operating system (OS), and the cryptographic service provider (CSP).

Applications communicate with the OS through the cryptographic application programming interface (CryptoAPI). The OS communicates with CSPs through the cryptographic service provider interface (CryptoSPI). The following illustration shows these concepts.

Cryptographic system architecture

All cryptographic operations are performed by independent modules known as cryptographic service providers (CSPs). A CSP is responsible for creating keys, destroying them, and using them to perform a variety of cryptographic operations. Each CSP provides a different implementation of the CryptoAPI. Some provide stronger cryptographic algorithms while others contain hardware components, such as smartcards.

At minimum, a CSP consists of, a dynamic-link library (DLL) and a signature file. The signature file ensures that the OS recognizes the CSP. The OS validates this signature periodically to ensure that the CSP has not been tampered with.

Each provider has both a name and a type. For example, the name of the CSP currently shipped with Windows CE is Microsoft Base Cryptographic Provider v1.0, and its type is PROV_RSA_FULL. The name of each provider is unique while the provider type is not.

The many different standards in cryptography are generally organized into groups known as families. Each family has its own set of data formats and protocols. Even if they use the same algorithm, two families will often use different cipher modes, key lengths, and default modes. In CryptoAPI, each CSP type represents a distinct family.

By default, when an application connects to a CSP of a particular type, each CryptoAPI function operates in a way prescribed by the family that corresponds to the CSP type. The following table shows the items that are specified by an application’s choice of CSP type.

CSP type properties
Description
Key exchange algorithm | Specifies one key exchange algorithm. Every CSP of a particular type must implement this algorithm. The only way applications can specify the key exchange algorithm is by selecting a CSP of the appropriate type.
Digital signature algorithm | This is the same as with the key exchange algorithm. Each CSP type specifies one digital signature algorithm.
Key binary large object (BLOB) format | Specifies the format of exported keys. Keys can be exported out of a CSP into a key BLOB to securely transfer keys between CSPs.
Digital signature format | Prescribes a particular digital signature format. This ensures that a signature produced by a CSP can be verified by any CSP of the same type.
Session key derivation scheme | Specifies the method used to derive session keys.
Key length | Specifes the length of keys.
Default modes | Specifies a default mode for various options, such as the block encryption cipher mode or the block encryption padding method.

For information about how to create your own CSP, see the Microsoft Platform SDK.


Key Databases

Each CSP has a key database in which it stores its persistent cryptographic keys. Each key database contains one or more key containers, each of which contains all the key pairs belonging to a specific user. The following illustration shows the relationship between CSPs, key databases, and key containers.

Contents of a key database

The CSP stores each key container from session to session, including all of the public and private key pairs it contains. However, session keys are not preserved from session to session.

Generally, a default key container is created for each user. This key container default key containers have a default name, a name which is then used by other applications. An application can create its own key container and key pairs, in which case, the key container is named after the application.


Key BLOBs

A key binary large object (BLOB) provides a way to a store key outside of the CSP. A BLOB is used as the medium for securely transferring a key from one provider to another. A key BLOB is fairly secure because it is encrypted with the key exchange public key of the intended recipient. To make it tamperproof, a key is sometimes signed with the key exchange private key of the originating user.

A key BLOB consists of a standard header followed by data that represents the key itself. Key BLOBs exist in three forms: simple, public, and private. A simple key BLOB, known as a SIMPLEBLOB, is a session key that has been encrypted with the public key exchange key of the destination user. Key exchange keys are used to encrypt session keys so that they can be safely stored and exchanged with other usersThis type of key BLOB is used when storing a session key or transmitting a session key to another user. For more information on key exchange, see Exchanging Cryptographic Keys.

A public key BLOB contains the public key portion of a public/private key pair. Unlike simple key blobs, these are not encrypted.

A private key BLOB contains one complete public and private key pair. These key BLOBs are used by administrative applications to distribute and transport public and private key pairs. For example, a private key BLOB transports key pairs between a network administrator’s computer and a user’s computer, or between a user’s desktop computer and his or her laptop computer. These key BLOBs can also be used by advanced applications to store key pairs themselves, rather than relying on the CSP’s storage mechanism.

For information on the formats of these key BLOBs, see the Microsoft Platform SDK.


Microsoft RSA Base Provider

Rsabase.dll, the Microsoft RSA Base Provider that is included with Windows CE, consists of a software implementation of the PROV_RSA_FULL provider type. PROV_RSA_FULL supports both digital signatures and data encryption, and is considered to be a general purpose cryptographic tool. For more information on PROV_RSA_FULL, see Microsoft Cryptographic Service Provider Programmer’s Guide, Microsoft, 1995, and RSA Laboratories Public-Key Cryptography Standards, RSA Data Security, November 1993.


Common Encryption Algorithms

The encryption algorithms available to an application depend on the cryptographic service provider (CSP) being used. Each of the encryption algorithms described below are supplied with the Microsoft RSA Base Provider.

The following table shows several encryption algorithms along with some performance benchmarks. These figures are for comparison purposes only. Your set up time and encryption speed may vary.

Cipher
Cipher Type
Key Setup Time (microseconds)
Encryption Speed (bytes/second)
DES | 64-bit block | 460 | 1,138,519
RC2 | 64-bit block | 40 | 286,888
RC4 | stream | 151 | 2,377,723

RC2 and RC4 are variable-key-length ciphers. However, when using CryptoAPI with the Microsoft RSA Base Provider, these key lengths are hard-coded to 40 bits.

Warning If you use the Microsoft RSA Base Provider to create a certification authority, your license to issue certificates is limited to certificates intended for use in the context of your particular application or service.


Using CryptoAPI

Applications can use the Microsoft CryptoAPI for the following tasks:

  • Connecting to a CSP
  • Generating cryptographic keys
  • Exchanging cryptographic keys
  • Encrypting and decrypting data
  • Creating digital signatures
  • All data encryption using CryptoAPI is performed with a symmetric algorithm, regardless of which CSP is installed.


Connecting to a CSP

The following table shows the functions that an application can use to connect to a CSP. These functions also allow applications to choose a specific CSP by name, or get one with a needed class of functionality.

Function
Description
CryptAcquireContext | Acquires a handle to the current user’s key container within a particular CSP.
CryptGetProvParam | Retrieves properties of a CSP.
CryptReleaseContext | Releases the handle acquired by CryptAcquireContext.
CryptSetProvider | Specifies the user default CSP for a particular CSP type.
CryptSetProvParam | Specifies properties of a CSP.

Each time an application is run, the first CryptoAPI function an application calls is the CryptAcquireContext function. This function returns to the application a handle to a particular CSP. In addition, this handle specifies a particular key container within the CSP. If the CSP has just been installed and no key containers yet exist, CryptAcquireContext can also be used to create a new one.

When an application uses CryptAcquireContext to obtain a CSP handle, it specifies a CSP type and, optionally, a provider name. If both a type and a name are specified, then the function looks for a CSP with precisely the same type and name, loads it into memory, and returns a handle to the application.

When an application calls CryptAcquireContext specifying a CSP type but no provider name, the function tries to find the provider name. It first searches a list of default providers associated with the logged-on user and, if that fails, it searches a list of default CSPs associated with your computer.

Once CryptAcquireContext has determined the provider name, it searches for the CSP, loads it into memory, and returns a handle to the application.


Generating Cryptographic Keys

The following table shows the functions that an application uses to generate cryptographic keys.

Function
Description
CryptDeriveKey | Generates a key derived from a password.
CryptGenKey | Generates a random key.

Although applications can create unlimited session keys, these keys are not preserved by the CSP from session to session. To preserve a key, export the key out of the CSP and import it into a key BLOB in the application’s memory space. For information on exporting and importing a key, see Exchanging Cryptographic Keys.

Session keys are created using either CryptGenKey or CryptDeriveKey. When a session key is generated, you must specify the algorithm to use for any subsequent encryption and decryption operations. This algorithm must be one of the symmetric algorithms supported by the CSP being used.

Because public-key algorithms are slow, it is impractical to use them to encrypt a large amount of data. In practice, symmetric algorithms are used for encryption and decryption, while public-key algorithms are used only to encrypt session keys.

For each user, the CSP usually maintains two public and private key pairs: the key exchange key pair, and the digital signature key pair. These keys are maintained from session to session.

There are a number of reasons for having two separate key pairs. For example, some CSPs use one algorithm for key exchange and another for digital signatures. Also, if some data, such as a session key, is both signed and encrypted with the same public key pair, subtle weaknesses can be introduced that make the data vulnerable.

The exchange key and the signature key pairs are created by calling the CryptGenKey function and specifying either AT_KEYEXCHANGE or AT_SIGNATURE. The CSP implements these keys in an application-independent manner. Applications are not permitted to know the details about the algorithm used.


Exchanging Cryptographic Keys

This section discusses those situations when you must export keys from the secure environment of the CSP into a key BLOB.

There are two occasions when it is necessary to export keys:

  • You want to save a session key for later use by your application.

    For example, if your application has just encrypted a database file and you want your application to decrypt this file at a later time, your application is responsible for storing the encryption key. This is necessary because CSPs do not preserve symmetric keys from session to session.

  • You want to send a key to someone else.

    This would be much easier for your application if the respective CSPs could communicate directly, but they cannot. This means the key has to be exported from your CSP, transmitted by your application to the destination application, and then imported into the destination CSP.

The following table shows the function that you can use to create, configure, and destroy cryptographic keys, and to exchange them with other users.

Function
Description
CryptDestroyKey | Destroys a key.
CryptExportKey | Exports a key from a CSP into a key BLOB in the application’s memory space.
CryptGenRandom | Generates random data, usually for salt values.
CryptGetKeyParam | Retrieves a key’s parameters.
CryptGetUserKey | Gets a handle to the key exchange or signature key.
CryptImportKey | Imports a key from a key BLOB into a CSP.
CryptSetKeyParam | Specifies a key’s parameters.


Storing Session Keys

To exchange cryptographic keys, it is necessary to first store the session key outside of the CSP for a certain period of time, because CSPs do not preserve session keys from session to session.

To store a session key for future use

  1. Create a simple key BLOB using the CryptExportKey function.

    This will transfer the session key from the CSP to your application’s memory space. Specify that your own key exchange public key be used to encrypt the key BLOB.

  2. Store the signed key BLOB to disk.

    All disks are considered to be nonsecure.

  3. Read the key BLOB from disk when you need to use the key.
  4. Import the key BLOB into the CSP using the CryptImportKey function.

If you plan to use the session key for encryption at a later time, then the key BLOB should be signed with your key exchange key before the key is stored to disk. When you later read the key BLOB from disk, you should validate the signature to make sure the key BLOB is intact. If these steps are omitted, then someone with access to your storage media can create their own session key, encrypt it with your key exchange public key, and substitute it for your key BLOB. You could then unknowingly use their session key to encrypt files and messages, which the unscrupulous user could then easily decrypt.

As an alternative to storing a random session key BLOB, you can use a derived session key, which is created from a password using the CryptDeriveKey function. In this way, instead of storing a particular derived key, an application can create a derived key as needed by prompting the user for the password.

A stored key BLOBs is dependent on the stability of the public and private key pairs stored within the CSP. If these key pairs are lost through a hardware or software incident, for example, you will be unable to decrypt your key BLOBs. Any data that has been encrypted using these keys will also be lost. For this reason, a user should consider using a backup authority when storing long-term archival data.


Using a Backup Authority

A backup authority is a trusted application running on a secure computer that provides storage for the session keys of its clients. All session keys stored there are encrypted, in the form of key BLOBs, with the backup authority’s public key.

To store session keys in a backup authority

  1. Encrypt the file normally.
  2. Export the session key used to encrypt the file into a simple key BLOB, specifying that your own key exchange public key be used to encrypt the key BLOB.
  3. Store this key BLOB with the encrypted file.
  4. Export the session key again, this time specifying that the backup authority’s public key be used to encrypt the key BLOB.
  5. Send this key BLOB to the backup authority, along with the key’s description, serial number, etc.

If, at a later time, you lose your key pairs, you can retrieve the session keys from a backup authority, although you will first have to establish your identity with the authority.


Exchanging Public Keys

Exchanging public keys is the first step that two users contemplating encrypted communication need to do. Once this has been done, the users can send encrypted and signed data to each other in a straightforward manner.

There are two ways to obtain each other’s public keys:

  • Each user can obtain the other’s keys in the form of certificates. This is the most secure way to exchange public keys that does not require user interaction. For more information on certificates, see Using Communications.
  • Users can read their public keys to each other over the telephone, use certified mail to send them to each other, or use another method that is tamperproof. Because your public key is not secret, it does not matter if it is overheard by a third party.

    This method can also be used to validate the public key values that have been exchanged in some other manner.

To exchange public keys, the sender exports his or her public key from the CSP into a public key BLOB, using the CryptExportKey function.

When the receiver has received the key BLOB data from the sender, the CryptImportKey function is used to import the key BLOB into its own CSP.


Exchanging Session Keys

To send another user an encrypted message, the sender must send the receiver the session key that was used to perform the encryption. There are two approaches to doing this:

  • The sender and receiver can mutually agree on a session key by exchanging several messages back and forth. The users can then use this session key to send encrypted messages back and forth. For a description of this approach and sample code for a three-phase key exchange protocol, see the Microsoft Platform SDK. Because successfully designing one of these protocols is difficult, consider it only if you are an experienced cryptographer.
  • The sender can send an encrypted session key along with the encrypted message. The sender creates a random session key, encrypts it using the receiver’s public key, and sends the key BLOB to the receiver along with the message. The receiver then decrypts the session key with his or her public key in order to decrypt the message.

To send an encrypted session key

  1. Create a random session key using the CryptGenKey function.
  2. Encrypt the message using the session key.

    For information on encrypting a message using a session key, see Encrypting and Decrypting Data.

  3. Export the session key into a key BLOB with the CryptExportKey function.

    Specify that the key be encrypted with the destination user’s key exchange public key, which is the receiver’s public key.

  4. Send both the encrypted message and the encrypted key BLOB to the destination user.
  5. The receiver then imports the key BLOB into the CSP using the CryptImportKey function.

    This automatically decrypts the session key, provided the destination user’s key exchange public key was specified in step 3.

  6. The receiver can then decrypt the message using the session key, following the procedure discussed in Encrypting and Decrypting Data.

The following illustration shows how to send an encrypted message using this procedure:

Sending an encrypted message together with a key BLOB

This approach is vulnerable in at least one way. An eavesdropper can acquire copies of one of more encrypted messages and the encrypted keys. Then, at some later time, the eavesdropper can send one of these messages to the receiver and the receiver will have no way of knowing that the message did not come directly from the original sender. This risk can be reduced by timestamping all messages or by using serial numbers. Timestamping involves attaching the date/time to each message. Using a three-phase key exchange protocol will eliminate this problem entirely. For information on using this protocol, see the Microsoft Platform SDK.


Encrypting and Decrypting Data

An encryption key is needed before invoking encryption and decryption operations. This key is obtained by using the functions CryptGenKey CryptDeriveKey, or CryptImportKey. The encryption algorithm is specified when the key is created. You can also specify additional encryption parameters using the CryptSetKeyParam function.

For information on CryptGenKey and CryptDeriveKey, see Generating Cryptographic Keys. For information on CryptImportKey, Exchanging Cryptographic Keys.

The following table shows the functions that you can use to encrypt and decrypt a message.

Function
Description
CryptEncrypt | Encrypts a section of plaintext using the specified encryption key.
CryptDecrypt | Decrypts a section of ciphertext using the specified encryption key.

To encrypt a file so that only the current user can access its data, bulk encrypt the file with a symmetric cipher. The key to this cipher is kept in a key BLOB that can only be opened with the user’s private key. This technique also works for encrypting messages for specific recipients.

To encrypt a message, a session key must first be generated using the CryptGenKey function. Calling this function generates a random key and returns a handle so the key can encrypt and decrypt data. You should specify the encryption algorithm at this point. Because CryptoAPI does not permit applications to use public-key algorithms to encrypt bulk data,call CryptGenKey to specify a symmetric algorithm, such as RC2 or RC4, for your application.

Alternatively, if your application needs to encrypt the message in such a way that anyone with a specified password can decrypt the data, the CryptDeriveKey function should be used to transform the password into a key suitable for encryption. In this case, CryptDeriveKey is called instead of the CryptGenKey function and the subsequent CryptExportKey calls are not needed.

Once the key is generated, other cryptographic properties of the key can be set with CryptSetKeyParam. For example, different sections of the file can be encrypted with different salt values and the cipher mode or initialization vector can be changed. Applications can generate salt values with the CryptGenRandom function.

In block ciphers, you can change the method of encryption by setting the block cipher properties with CryptSetKeyParam. The following table shows the ciper modes.

Ciper mode
Description
Electronic cookbook (ECB) | Encrypts blocks individually. No feedback is used.
Cipher block chaining (CBC) | Encrypts blocks using feedback to ensure uniqueness.
Cipher feedback mode (CFB) | Encrypts small increments of plaintext at a time, not entire blocks.
Output feedback mode (OFB) | Encrypts similarly to CFB, but uses a different method for filling shift registers.

Electronic cookbook (ECB): In this cipher mode, each block is encrypted individually and no feedback is used. This means that identical blocks of plaintext encrypted with the same key are transformed into identical ciphertext blocks. If a single bit of the ciphertext block is garbled, then the entire corresponding plaintext block is also garbled.

Cipher block chaining (CBC): Each plaintext block in this cipher mode is encrypted based on the ciphertext of the previous block. CBC ensures that even if the plaintext contains many identical blocks, they each encrypt to a different ciphertext block. Similarly to EBC, if a single bit of the ciphertext block is garbled, then the corresponding plaintext block will also be garbled. Moreover, a bit in the subsequent plaintext block, which isin the same position as the original garbled bit, will be garbled. If there are extra or missing bytes in the ciphertext, the plaintext will be garbled from that point on.

Cipher feedback mode (CFB): In this cipher mode, small increments of plaintext can be processed into ciphertext, instead of processing entire blocks at a time. CFB is useful in some situations. For example, data originating from a keyboard can be encrypted at each keystroke without waiting for an entire block to be typed.

This mode uses a shift register that is one block size in length and divided up into sections. For example, if the block size is 64 bits with 8 bits processed at a time, then the shift register is divided into eight sections.

CFB follows this process for each encryption cycle:

  1. The shift register is filled with the initialization vector.
  2. The block in the shift register is encrypted.
  3. The leftmost 8 bits in the encrypted shift register are matched with the next 8 bits of plaintext and sent off as 8 bits of ciphertext.
  4. The shift register shifts 8 bits to the left.
  5. The 8 bits of ciphertext generated in step 2 is placed in the rightmost 8 bits of the shift register.
  9. In CryptoAPI, the number of bits processed at a time is specified by setting the encryption key’s KP_MODE_BITS parameter using the CryptSetKeyParam function. The default value for this parameter is typically 8 bits.
  10. If 1bit in the ciphertext is garbled, then 1 bit in the plaintext is garbled and the shift register is corrupted. This corruption results in the corrupting of subsequent plaintext blocks until the bad bit is shifted out of the shift register.
  15. Output feedback mode (OFB): This cipher mode is identical to CFB, except the shift register is filled differently. If 1 bit in the ciphertext is garbled, the corresponding bit of plaintext is also garbled. If there are extra or missing bits from the ciphertext, then the plaintext isgarbled from that point on.

If the application does not explicitly specify one of these modes, then CBC is used.

Encrypt the data in the file with the CryptEncrypt function, which takes the previously generated session key and encrypts a buffer of data. As the data is encrypted, it may be slightly expanded by the encryption algorithm. The application is responsible for remembering the length of the encrypted data so the proper length can later be given to the CryptDecrypt function.

To allow the user to decrypt the data in the future, the CryptExportKey function is used to save the decryption key in a key BLOB that can only be decrypted with the user’s private key. This function requires the user’s key exchange public key for this purpose, which can be obtained by using the CryptGetUserKey function. The CryptExportKey function returns a key BLOB that must be stored by the application for use in decrypting the file.

If your application has certificates or public keys for other users, it can permit other users to decrypt the file by performing CryptExportKey calls for each user to whom it wants to give access. The returned key BLOBs must be stored by the application, as in the previous paragraph.

Once a file or message has been encrypted, the following data must be stored by the application. This data is usually bundled together.

  • The encrypted data
  • One or more key BLOBs, each containing the session key used to encrypt the message
  • Any salt values that were specified as the data was being encrypted
  • Any initialization vectors that were specified as the data was being encrypted

All parameters that were specified with the CryptSetKeyParam function as the message was being encrypted must also be specified as the message is decrypted. It may be appropriate to store some of these parameters with the encrypted message as well.

The following code example reads data from a text file named test1.txt, encrypts it using the RC2 block cipher, and writes out the encrypted data to a file named test1.xxx. A random session key is generated to perform the encryption and is stored to the output file along with the encrypted data. This session key is encrypted with the user's key exchange public key by the CryptExportKey function.

#include <wincrypt.h>

FILE *hSource = NULL;
FILE *hDest = NULL;
int eof = 0;

HCRYPTPROV hProv = 0;
HCRYPTKEY hKey = 0;
HCRYPTKEY hXchgKey = 0;

#define BLOCK_SIZE 160
#define BUFFER_SIZE (BLOCK_SIZE+16) // Give buffer 16 bytes of extra
                                    // room for padding, etc.
BYTE pbBuffer[BUFFER_SIZE];
DWORD dwCount;

BYTE *pbKeyBlob = NULL;
DWORD dwBlobLen;

// Open source file.
if((hSource=fopen("test1.txt","rb"))==NULL) {
    printf("Error opening source file!\n");
    goto done;
}

// Open destination file.
if((hDest=fopen("test1.xxx","wb"))==NULL) {
    printf("Error opening destination file!\n");
    goto done;
}

// Get handle to the default provider.
if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) {
    printf("Error %x during CryptAcquireContext!\n", GetLastError());
    goto done;
}

// Get handle to key exchange key.
if(!CryptGetUserKey(hProv, AT_KEYEXCHANGE, &hXchgKey)) {
    printf("Error %x during CryptGetUserKey!\n", GetLastError());
    goto done;
}

// Create a random block cipher session key.
if(!CryptGenKey(hProv, CALG_RC2, CRYPT_EXPORTABLE, &hKey)) {
    printf("Error %x during CryptGenKey!\n", GetLastError());
    goto done;
}

// Determine size of key blob and allocate memory.
if(!CryptExportKey(hKey, hXchgKey, SIMPLEBLOB, 0, NULL, &dwBlobLen)) {
    printf("Error %x computing blob length!\n", GetLastError());
    goto done;
}
if((pbKeyBlob = malloc(dwBlobLen)) == NULL) {
    printf("Out of memory!\n");
    goto done;
}

// Export key into a simple key blob.
if(!CryptExportKey(hKey, hXchgKey, SIMPLEBLOB, 0, pbKeyBlob, &dwBlobLen)) {
    printf("Error %x during CryptExportKey!\n", GetLastError());
    free(pbKeyBlob);
    goto done;
}

// Write size of key blob to destination file.
fwrite(&dwBlobLen, sizeof(DWORD), 1, hDest);
if(ferror(hDest)) {
    printf("Error writing header!\n");
    free(pbKeyBlob);
    goto done;
}

// Write key blob to destination file.
fwrite(pbKeyBlob, 1, dwBlobLen, hDest);
if(ferror(hDest)) {
    printf("Error writing header!\n");
    free(pbKeyBlob);
    goto done;
}

// Free memory.
free(pbKeyBlob);

// Encrypt source file and write to destination file.
do {
    // Read up to BLOCK_SIZE bytes from source file.
    dwCount = fread(pbBuffer, 1, BLOCK_SIZE, hSource);
    if(ferror(hSource)) {
        printf("Error reading data!\n");
        goto done;
    }
    eof=feof(hSource);

    // Encrypt data
    if(!CryptEncrypt(hKey, 0, eof, 0, pbBuffer, &dwCount, BUFFER_SIZE)) {
        printf("Error %x during CryptEncrypt!\n", GetLastError());
        goto done;
    }

    // Write data to destination file.
    fwrite(pbBuffer, 1, dwCount, hDest);
    if(ferror(hDest)) {
        printf("Error writing data!\n");
        goto done;
    }
} while(!feof(hSource));

done:

// Destroy session key.
if(hKey != 0) CryptDestroyKey(hKey);

// Destroy key exchange key.
if(hXchgKey != 0) CryptDestroyKey(hXchgKey);

// Release provider handle.
if(hProv != 0) CryptReleaseContext(hProv, 0);

// Close source file.
if(hSource != NULL) fclose(hSource);

// Close destination file.
if(hDest != NULL) fclose(hDest);

If a message was encrypted for a particular user, then the CryptGenKey function was used to create a random session key before the encryption was performed. Before decrypting the message, the key BLOB containing the session key must be imported into the CSP with the CryptImportKey function. This function will use the user's key exchange private key to decrypt the key BLOB and ensure that the originating key BLOB was created using the matching key exchange public key.

If the message was encrypted so that any password holder can access the data, the CryptImportKey function is not used. Instead, create the decryption session key with the CryptDeriveKey function. You will also need to supply the function with the password or other access token.

The session key’s parameters must be configured in the same way as it was when the encryption was performed. These parameters can be specified using the CryptSetKeyParam function. For example, if the salt value was changed one or more times during the encryption process, then it must also be changed during the decryption process in exactly the same manner.

The message is decrypted using the CryptDecrypt function. If the message is too large to fit comfortably in memory, it can be decrypted in sections, through multiple calls to CryptDecrypt.

When the decryption is complete, be sure to destroy the session key, using the CryptDestroyKey function. In addition to destroying the key, this frees CSP resources.

The following code example decrypts the file created by the previous example of encryption. This decryption example uses the RC2 block cipher, and writes out the plaintext data to a file named test1.txt. The session key used to perform the decryption is read from the ciphertext file.

#include <wincrypt.h>

FILE *hSource = NULL;
FILE *hDest = NULL;
int eof = 0;

HCRYPTPROV hProv = 0;
HCRYPTKEY hKey = 0;

#define BLOCK_SIZE 160
BYTE pbBuffer[BLOCK_SIZE];
DWORD dwCount;

BYTE *pbKeyBlob = NULL;
DWORD dwBlobLen;

// Open source file.
if((hSource=fopen("test1.xxx","rb"))==NULL) {
    printf("Error opening source file!\n");
    goto done;
}

// Open destination file.
if((hDest=fopen("test1.txt","wb"))==NULL) {
    printf("Error opening destination file!\n");
    goto done;
}

// Get handle to the default provider.
if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 0)) {
    printf("Error %x during CryptAcquireContext!\n", GetLastError());
    goto done;
}

// Read key blob length from source file and allocate memory.
fread(&dwBlobLen, sizeof(DWORD), 1, hSource);
if(ferror(hSource) || feof(hSource)) {
    printf("Error reading file header!\n");
    goto done;
}
if((pbKeyBlob = malloc(dwBlobLen)) == NULL) {
    printf("Out of memory!\n");
    goto done;
}

// Read key blob from source file.
fread(pbKeyBlob, 1, dwBlobLen, hSource);
if(ferror(hSource) || feof(hSource)) {
    printf("Error reading file header!\n");
    goto done;
}

// Import key blob into CSP.
if(!CryptImportKey(hProv, pbKeyBlob, dwBlobLen, 0, 0, &hKey)) {
    printf("Error %x during CryptImportKey!\n", GetLastError());
    goto done;
}

// Decrypt source file and write to destination file.
do {
    // Read up to BLOCK_SIZE bytes from source file.
    dwCount = fread(pbBuffer, 1, BLOCK_SIZE, hSource);
    if(ferror(hSource)) {
        printf("Error reading data from source file!\n");
        goto done;
    }
    eof=feof(hSource);

    // Decrypt data.
    if(!CryptDecrypt(hKey, 0, eof, 0, pbBuffer, &dwCount)) {
        printf("Error %x during CryptDecrypt!\n", GetLastError());
        goto done;
    }

    // Write data to destination file.
    fwrite(pbBuffer, 1, dwCount, hDest);
    if(ferror(hDest)) {
        printf("Error writing data to destination file!\n");
        goto done;
    }
} while(!feof(hSource));

done:

// Free memory.
if(pbKeyBlob) free(pbKeyBlob);

// Destroy session key.
if(hKey != 0) CryptDestroyKey(hKey);

// Release provider handle.
if(hProv != 0) CryptReleaseContext(hProv, 0);

// Close source file.
if(hSource != NULL) fclose(hSource);

// Close destination file.
if(hDest != NULL) fclose(hDest);


Encrypting and Decrypting Simultaneously

When encrypting or decrypting data simultaneously with the same key, the same physical session key must not be used for both operations. This is because every session key contains internal state information that will become jumbled if used for more than one operation at a time. A simple solution to this problem is to make a copy of the session key so that the original key can be used for one operation and the copy used for the other.

Copying a session key is done by exporting the key with CryptExportKey and then using CryptImportKey to import it back in. When the key is imported, the CSP will give the imported key its own section of internal memory, as if it were not related to the original key.

The following code example shows how a copy of a session key can be obtained.

HCRYPTPROV hProv;    // Handle to a CSP.
HCRYPTKEY hKey;      // Handle to a session key.

HCRYPTKEY hCopyKey = 0;
HCRYPTKEY hPubKey = 0;
BYTE pbBlob[256];
DWORD dwBlobLen;

// Get a handle to your own key exchange public key.
CryptGetUserKey(hProv, AT_KEYEXCHANGE, &hPubKey);

// Export the session key into a key blob.
dwBlobLen = 256;
CryptExportKey(hKey, hPubKey, SIMPLEBLOB, 0, pbBlob, &dwBlobLen);

// Import the session key back into the CSP. This is stored separately
// from the original session key.
CryptImportKey(hProv, pbBlob, dwBlobLen, 0, 0, &hCopyKey);

// Use ‘hKey’ for one set of operations and ‘hCopyKey’ for the other.
...

This technique should not be used with stream ciphers because stream cipher keys should never be used more than once. Instead, use separate keys to transmit and receive data.


Creating Digital Signatures

The following table shows the functions that applications can use to compute cryptographically secure digests of data and to create and verify digital signatures.

Function
Description
CryptCreateHash | Creates an empty hash object.
CryptDestroyHash | Destroys a hash object.
CryptGetHashParam | Retrieves a hash object parameter.
CryptHashData | Hashes a block of data, adding it to the specified hash object.
CryptHashSessionKey | Hashes a session key, adding it to the specified hash object.
CryptSetHashParam | Sets a hash object parameter.
CryptSignHash | Signs the specified hash object.
CryptVerifySignature | Verifies a digital signature, given a handle to the hash object that was signed.

To create a digital signature from a message, create a hash value, also known as a message digest, from the message. Then, use the private key of the signer to sign the hash value. The following illustration shows the process for creating a digital signature.

Creating a digital signature

To verify a digital signature, both the message and the signature are required. First, a hash value must be created from the message in the same way as it was done when the signature was created. This hash value is then verified against the signature, using the public key of the signer. If the hash value and the signature match, you can be confident that the message is the one originally signed and that it has not been tampered with. The following illustration shows the process of verifying a digital signature.

Verifying a digital signature

A hash value consists of a small amount of binary data, typically 160 bits. It is produced using a hashing algorithm.

All hash values share the following properties, regardless of the algorithm used:

  • A hash value is of a fixed length, regardless of the size of the message.
  • Every pair of nonidentical messages translates into a different hash value, even if the two messages differ only by a single bit. Using today’s technology, it is not feasible to discover a pair of messages that translate to the same hash value without breaking the hashing algorithm.
  • All hashing algorithms are fully deterministic. That is, each time a particular message is hashed using the same algorithm, the exact same hash value is produced.
  • All hashing algorithms are one-way. Given a hash value, it is not possible to recover the original message. In fact, none of the properties of the original message can be determined given the hash value alone.


Signing and Verifying Messages

In order to sign data, a hash object must first be created using the CryptCreateHash function. This object accumulates the data to be signed. Next, the data is added to the hash object with the CryptHashData function.

After the last block of data is added to the hash, the CryptSignHash function is used to sign the hash. A description of the data can also be added to the hash object at this point. Once the digital signature data has been obtained, the hash object should be destroyed with the CryptDestroyHash function.

Hashes can be signed with either the signature private key or the key exchange private key. The signature key should be used when the user who owns the signature key is signing some of his or her data. The key exchange key should be used when signing data that does not directly belong to the user. The classic example of this is when the exchange key is used to sign session keys during a key exchange protocol.

To verify a signature, applications must first create a hash object using the CryptCreateHash function. This object accumulates the data to be verified. The data is then added to the hash object with the CryptHashData function.

After the last block of data is added to the hash, the CryptVerifySignature function is used to verify the signature. The signature data, a handle to the hash object, and the description string must all be supplied to CryptVerifySignature. A handle to the key pair that was used to sign the data must also be specified.

Once the signature has been verified, or has failed the verification, the hash object should be destroyed with the CryptDestroyHash function.

To obtain the hash value, a hash object must first be created using the CryptCreateHash function. This object accumulates the data to be verified. The data is then added to the hash object with the CryptHashData function.

After the last block of data is added to the hash, the CryptGetHashParam function is used to obtain the hash value.

Once the hash value has been obtained, the hash object should be destroyed with the CryptDestroyHash function.

Because CryptoAPI handles the actual method of doing the signature, applications do not need to be aware of how the signature is applied.


Hashing and Digital Signature Algorithms

This section lists several algorithms used to compute hashes and digital signatures. Each of these algorithms is supported by the Microsoft RSA Base Provider.

The MD2, MD4, and MD5 hashing algorithms were all developed by RSA Data Security, Inc. These algorithms were developed in sequential order. The later algorithms are generally more secure than the earlier ones. All three algorithms generate 128-bit hash values.

The secure hashing algorithm (SHA) was developed by the National Institute of Standards and Technology (NIST) and by the National Security Agency (NSA). This algorithm was developed for use with Digital Signature Algorithm (DSA) or Digital Signature Standard (DSS). This algorithm generates a 160-bit hash value.

Message Authentication Codes (MACs) are similar to hash values, but are computed using a session key. Because of this , you must possess the session key to recompute the hash value to verify that the base data has not changed.

The MACs implemented by the Microsoft RSA Base Provider are of the most common sort. That is, they are block cipher MACs. This method encrypts the base data with a block cipher and then uses the last encrypted block as the hash value. The encryption algorithm used to build the MAC is the one that was specified when the session key was created.

Warning The same session key should not be used for both message encryption and MAC generation. Doing so greatly increases the risk of your messages being decoded.


Administrating CryptoAPI Use

This section discusses how multiple cryptographic service providers (CSPs) can be installed on a computer and the default providers specified. The structure of the registry is also mentioned.

New providers are installed by running their setup applications. This copies the CSP files to the appropriate directories and makes all needed changes to the registry.


Overview of CryptoAPI Registry Use

CryptoAPI uses the registry to store a database of the CSPs that have been installed on the computer. Both the computer default providers and the user- installed providers are recorded here.

Warning This section is included for informational purposes only. The details of CryptoAPI’s registry use may change at any time. Under no circumstances should an application read from or alter the registry directly.

The following outline shows the portions of the registry used by CryptoAPI. Some sample entries are also shown.

HKEY_LOCAL_MACHINE
 SOFTWARE
 Microsoft
 Cryptography
 Defaults
 Provider
 Microsoft Base Cryptographic Provider v1.0
>Image Path:REG_SZ:rsabase.dll
>Signature:REG_BINARY:<digital signature>
>Type:REG_DWORD:0x1
John’s Provider
>Image Path:REG_SZ:johncsp.dll
>Signature:REG_BINARY:<digital signature>
>Type:REG_DWORD:0x2a
Provider Types
 Type 001
 >Name:REG_SZ:Microsoft Base Cryptographic Provider v1.0
Type 042
 >Name:REG_SZ:John’s Provider

HKEY_CURRENT_USER
 Software
 Microsoft
 Cryptography
 Providers
 Type 001
 >Name:REG_SZ:Microsoft Base Cryptographic Provider v1.0

Entries under the HKEY_LOCAL_MACHINE\...\Provider key contain information about all of the CSPs that have been installed on the computer. These entries are created by the setup application used to install a new CSP. These entries are organized under subkeys whose names indicate the provider name.

Entries under the HKEY_LOCAL_MACHINE\...\Provider Types key contain the name of the machine default CSP for each provider type. These entries are also created by the setup application used to install a new CSP. These entries are organized under subkeys whose names, appearing in decimal format, indicate the provider type.

Entries under the HKEY_CURRENT_USER\...\Providers key contain the name of the current user default CSP for each provider type. These entries are created/modified by the CryptSetProvider function. These entries are organized under subkeys whose names indicate the provider type.


Security Support Provider Interface

As intranets become more secure, client applications, such as Web browsers and e-mail application, and their servers become more complicated. Different types of applications require different ways of identifying or authenticating users and different ways of protecting or encrypting data as it travels across a network. In order to avoid coding every available security option into an application, Windows CE supports the Security Support Provider Interface (SSPI), which enables applications to access dynamic-link-libraries (DLLs) containing common authentication and cryptographic data schemes. These DLLs are called Security Support Providers (SSPs).

SSPs make available to applications one or more security solutions, called security packages. A security package map various SSPI functions to the security protocols specified in the package. An application implementing the SSPI can use any security package available on a system without knowing details about the security protocols that the security package implements. The application programming interfaces (APIs) contained in the SSPI are divided into the following functional areas:

  • Package management
  • Credential management
  • Context management
  • Message support

Package management functions enumerate and query the attributes of the security packages of a security provider. They list the security packages available on a system and enable an application to select one to support its needs.

Credential management functions enable applications to gain access to the credentials of a principal. A principal is an entity recognized by the security system. This includes human users as well as autonomous processes. A credential is data used by a principal to establish the identity of the principal, such as a password or user name.

Context management functions enable applications to create and use security contexts. A security context is the security data relevant to a connection, and contains such information as a session key and the duration of a session. Both the client and the server in a communication link must cooperate to create a security context. The client and the server can then use the security context with the message support functions to ensure message integrity and privacy during the connection.

Message support functions enable an application to transmit messages that cannot be tampered with. The functions work with one or more buffers that contain a message and an associated security context created by the context management functions.

This chapter describes how to initialize and use the functions contained in the SSPI to create a secure network connection setup. This process contains the following primary tasks:

  • Initializing the SSPI
  • Establishlishing an authenticated connection
  • Ensuring communication integrity during message exchange

To install a security provider, update the registry as follows:

HKEY_LOCAL_MACHINE\Comm\SecurityProviders
Providers = REG_SZ provider1.dll, provider2.dll,...

A single DLL can contain multiple providers. Provider.dll can contain two security packages; for example, Protocol 1 and Protocol 2.


Initializing the SSPI

Before initializing the SSPI, an application must load the security provider using the LoadLibrary function.

The following code example shows how to load the security provider DLL.

void    *    DllHandle;
//loading NTLM SSP
DllHandle = (void *)LoadLibrary(TEXT("security.dll");
if(!DllHandle)
{
    //
    // DLL did not get loaded.
    //
    Status = GetLastError();
    return Status;
}
//
// DllHandle is valid.
//

Once the security service provider has been loaded successfully, the client and the server call the GetProcAddress function to get a pointer to the initialization function for the provider called InitSecurityInterface. You can use InitSecurityInterface to return a pointer to the SecurityFunctionTable. The table contains pointers to information about the security packages or protocols supported by the security provider.

Before using a specific security package, both the client and the server should call the EnumerateSecurityPackages function to enumerate the security packages that are available from the security provider. The EnumerateSecurityPackages function returns an array of SecPkgInfo structures that describe the attributes of the available security packages. A client or server can also use the QuerySecurityPackageInfo function to determine the attributes of the specified security package.

The following code example shows how to initialize the SSPI.

// Initial provider setup.
//
INIT_SECURITY_INTERFACE     InitSecurityInterface;
PSecurityFunctionTable     SecurityInterface = 0;
SecPkgInfo     PAPI *          SecurityPackages;
DWORD             NumOfPkgs;
SECURITY_PROVIDER_INFO PAPI * List;
InitSecurityInterface = GetProcAddress(DllHandle, SECURITY_ENDPOINT);
if(!InitSecurityInterface)
{
    //
    // If something goes wrong..
    //
}
// We got the InitSecurityInterface!
// Use it to get the function table.
//
SecurityInterface = (*InitSecurityInterface)();
if(!SecurityInterface)
{
    //
    // If something goes wrong..
    //
}
// Find out if the security packages supported by the provider.
//
Status = (*SecurityInterface->EnumerateSecurityPackages)( &NumOfPkgs, &SecurityPackages);
// Using the capabilities information, figure out which package you want // to use.
PkgToUseIndex = -1;
for(I=0;I<NumOfPackages;I++)
{
    //
    // If an application needs integrity and privacy on messages, it
    // checks:
if(SecurityPackages[I].fCapabilities & (SECPKG_FLAG_INTEGRITY | SECPKG_FLAG_PRIVACY))
    {
        PkgToUseIndex = I;
        break;
    }
}
if(PkgToUseIndex > 0)
{
    //
    // Find out the maximum token size for this package
    //
    g_MaxToken = SecurityPackages[I].cbMaxToken;
}


AAuthenticating aing a Connection

Authenticating a Connection

In a typical client/server application protocol, the server waits for the client to connect and request service. Upon connection, the server must be able to authenticate the client and the client must be able to authenticate the server. The protocol used to establish an authenticated connection involves the exchange of one or more security tokens between the client’s security provider and the server’s security provider. The tokens are sent as opaque messages by the client and the server along with any other protocol-specific information. When a message is received, the application protocol strips the token out of the message and gives it to its security provider to make sure the authentication is complete or if further exchange of tokens is required. In general, the client and the server continue to exchange messages until either the authentication is successfully established, or an error occurs.

To begin the authentication process, the client needs to obtain an outbound credentials handle so that it can send an authentication request to the server. This is accomplished by calling the AcquireCredentialsHandle function.

Using a reference to the SecurityFunctionTable initialized during the SSPI initialization procedure, the following code example shows how the client obtains a credentials handle.

//
// Acquire an out-bound Credentials handle using the chosen security //package.
SecurityStatus = (*SecurityInterface->AcquireCredentialsHandle)(
                    0,
                    SecurityPackages[PkgToUseIndex].Name,
                    SECPKG_CRED_OUTBOUND,
                    0,
                    0,
                    0,
                    0,
                    &Credentials,
                    &TimeStamp
                    );

Once the client accesses credential information, it starts the authentication protocol to establish a connection with the server. The client calls the InitializeSecurityContext function to obtain a security token to send in its connection request message to the server.

The following code example shows how the client calls the InitializeSecurityContext function.

//
// Set up the Buffer Descriptor.
//
OutBufferDesc.ulVersion = 0;
OutBufferDesc.cBuffers = 1;
OutBufferDesc.pBuffers = &OutSecBuffer;
OutSecBuffer.cbBuffer = BufferLen;
OutSecBuffer.BufferType = SECBUFFER_TOKEN;
OutSecBuffer.pvBuffer = Buffer;
//
// Get the authentication token from the security package
// to send to the server to request an authenticated connection.
//
SecurityStatus = (*SecurityInterface->InitializeSecurityContext(
                Credentials,
                0,
                ServerPrincipalName,
                ISC_REQ_USE_DCE_STYLE | ISC_REQ_DELEGATE |
                ISC_REQ_MUTUAL_AUTH |ISC_REQ_REPLAY_DETECT |
                ISC_REQ_SEQUENCE_DETECT |ISC_REQ_CONFIDENTIALITY |
                ISC_REQ_CONNECTION,
                0,
                0,
                0,
                0,
                &SecurityContext,
                BufferDescriptor,
                &ContextAttributes,
                &TimeStamp
                );

The client uses the security token information received in the output buffer descriptor to generate a message to send to the server. The construction of the message, in terms of placement of various buffers, is decided by the application protocol and is adhered to by the two parties.

Most SSPI functions have variable length arguments that allow an application to provide message data to a security package and allow a security package to return security data, such as a token, to the application. These functions use a parameter type, called a buffer descriptor, to define the size and location of the variable length data. For more information on buffers, see Memory Use and Buffers.

Once the client sends its message to the server, it checks the return status from the call to InitializeSecurityContext to see if authentication is complete. If not, it expects to receive an authentication token from the server in a response message to continue the security protocol. The return status SEC_I_CONTINUE_NEEDED, indicates that the security protocol requires additional authentication messages.

To establish an authenticated connection, the server must also obtain a handle to its credentials by calling the AcquireCredentialsHandle function. Once received, you should assign a global variable to the handle to use for the lifetime of the server process.

When the server recives a connection request from the client, it calls the AcceptSecurityContext function., passing as input the data that it received from the client. The server initializes the security buffer descriptors to refer to specifc sections of the data, rather than copying the data to an alternate buffer.

The following code example shows how to implement the AcceptSecurityContext function.

//
// Set up the Input and OutputBuffer Descriptor using the information
// from message received from the client.
//
OutBufferDesc.ulVersion = 0;
OutBufferDesc.cBuffers = 1;
OutBufferDesc.pBuffers = &OutSecBuffer;

OutSecBuffer.cbBuffer = BufferLen;
OutSecBuffer.BufferType = SECBUFFER_TOKEN;
OutSecBuffer.pvBuffer = Buffer;

InBufferDesc.ulVersion = 0;
InBufferDesc.cBuffers = 1;
InBufferDesc.pBuffers = &InSecBuffer;

InSecBuffer.cbBuffer = InBufferLen;
InSecBuffer.BufferType = SECBUFFER_TOKEN;
InSecBuffer.pvBuffer = InBuffer;
//
// Initialize the client's context from the SSP and see if
// you need to send anything back to the client
//
SecurityStatus = (*SecurityInterface->AcceptSecurityContext(
                Credentials,
                0,
                InputBufferDescriptor,
                ISC_REQ_USE_DCE_STYLE | ISC_REQ_DELEGATE |
                ISC_REQ_MUTUAL_AUTH |ISC_REQ_REPLAY_DETECT |
                ISC_REQ_SEQUENCE_DETECT |ISC_REQ_CONFIDENTIALITY |
                ISC_REQ_CONNECTION,
                DataRepresentation,
                &SecurityContext,
                OutputBufferDescriptor,
                &ContextAttributes,
                &TimeStamp
                );

The server checks the return status and output buffer descriptor to ensure that there are no errors. If errors exist, it rejects the connection request. If no errors exist, it checks the output buffer for information. If information is present in the buffer, the server bundles the information according to the application protocol and places it into a response message to the client.

If the return status is SEC_I_CONTINUE_NEEDED or SEC_I_COMPLETE_AND_CONTINUE, another message exchange with the client is required. Otherwise, authentication is complete. If authentication must continue, the server waits for the client to respond with another message. You should place a timeout on this waiting period in case the client does not respond with a message. This will avoid the possibility of hanging this, and subsequently all, server threads.

Once the client receives a reply from the server, it deconstructs the message and calls InitializeSecurityContext again, making sure to use the continue status from the previous call. The server responds to the client’s message by repeating its call to the AcceptSecurityContext function. Depending on the security package and the context requirements, the transmission of messages between the client and the server can go on indefinitely, but is usually limited to three attempts


Context Semantics

The SSPI supports three types of security contexts: connection, datagram, and stream contexts.

With a connection context, the caller of the function is responsible for formatting messages. The caller also relies on the security provider to authenticate connections and to ensure the integrity of specific parts of the message. Most of the range of context options are available to connection contexts. These options include mutual authentication, replay detection, and sequence detection. A security package sets the SECPKG_FLAG_CONNECTION flag to indicate that it supports connection semantics.

A datagram context, or connectionless context, has slightly different semantics from a connection context. A connectionless context implies that the server has no way of determining when the client has shut down or otherwise terminated the connection. In other words, no termination notice is passed from the transport application to the server, as would occur in a connection context. A security package sets the SECPKG_FLAG_DATAGRAM flag to indicate that it supports datagram semantics. If a client specifies the ISC_REQ_DATAGRAM flag in its call to the InitializeSecurityContext function, the following characteristics apply:

  • The security package does not produce an authentication binary large object (BLOB) on the first call to the InitializeSecurityContext function. However, the client can immediately use the returned security context in a call to the MakeSignature function to generate a signature for a message.
  • The security package must allow for the context to be re-established multiple times to allow the server to drop the connection without notice. This also implies that any keys used in the MakeSignature and VerifySignature functions can be reset to a consistent state.
  • The security package must allow for the caller to specify sequence information, and must allow the receiver to return that same sequence information back to the caller. This is not exclusive of any sequence information maintained by the package.

A stream context is different from a connection context and a datagram context. A stream context handles secure stream protocols. A security package that supports stream contexts has the following characteristics:

  • The package sets the SECPKG_FLAG_STREAM flag to indicate that it supports stream semantics, just as it would set a flag to indicate support for connection and datagram semantics.
  • A transport application requests stream semantics by setting the ISC_REQ_STREAM and ASC_REQ_STREAM flags in the calls to the InitializeSecurityContext and AcceptSecurityContext functions.
  • The application calls the QueryContextAttributes function with a SecPkgContext_StreamSizes structure to query the security context for the number of buffers to provide, and the sizes to reserve for headers or trailers.
  • The application provides extra buffer descriptors during the actual processing of the data.

By specifying stream semantics, the caller indicates a willingness to do extra work so that the security provider can block messages. This extra work entails passing in a list of buffers when the MakeSignature and VerifySignature functions are called. When a message is received from a channel that is stream-oriented, the caller passes in a buffer. The following table shows the buffer:

Buffer
Length
Buffer type
1 | MessageLength | SECBUFFER_DATA
2 | 0 | SECBUFFER_EMPTY
3 | 0 | SECBUFFER_EMPTY
4 | 0 | SECBUFFER_EMPTY
5 | 0 | SECBUFFER_EMPTY

The security package then authenticates the BLOB. The following table shows what the buffer list looks like, if the function returns successfully.

Buffer
Length
Buffer type
1 | Header Length | SECBUFFER_STREAM_HEADER
2 | Data Length | SECBUFFER_DATA
3 | Trailer Length | SECBUFFER_STREAM_TRAILER
4 | 0 | SECBUFFER_EMPTY
5 | 0 | SECBUFFER_EMPTY

The following table shows an alternate return value for buffer 4.

Buffer
Length
Buffer type
4 x SECBUFFER_EXTRA

The buffer listed in the previous table indicates that the data in this buffer is part of the next record, and has not yet been processed.

Conversely, the following table shows what the returned buffer list would look like if the message function returns the SEC_E_INCOMPLETE_MESSAGE error code.

Buffer
Length
Buffer type
1 x SECBUFFER_MISSING

The buffer described in the previous table indicates that more data is needed to process the record. Unlike most errors returned from a message function, this buffer type does not indicate that the context has been compromised; it indicates that more data is needed. Security providers must not update their state in this condition.

Similarly, on the sender’s side of the communication, the caller can call the MakeSignature function, in which case the security package may need to reallocate the buffer. The following table shows the buffer list that the caller can provide to be more efficient.

Buffer
Length
Buffer type
1 | Header Length | SECBUFFER_STREAM_HEADER
2 | Data Length | SECBUFFER_DATA
3 | Trailer Length | SECBUFFER_STREAM_TRAILER

Using a buffer like the one described in the previous table, allows the caller to use the buffers more efficiently. By calling the QueryContextAttributes function to determine the amount of space to reserve before calling MakeSignature, the operation is more efficient for the application and the security package.


Context Requirements

Context requirements are expressed as a combination of bit flags that are passed to either the InitializeSecurityContext or AcceptSecurityContext function. These flags affect the context in a number of ways: not all flags apply to all contexts, some flags are valid only for the server, and other flags are valid only for the client.

The caller uses the fContextReq parameter of the InitializeSecurityContext or AcceptSecurityContext function to specify a set of flags that indicates the required capabilities. When the function returns, the pfContextAttr parameter indicates the attributes of the established context. The caller is responsible for determining whether the final context attributes are acceptable. For example, if the caller requested mutual authentication, but the security package indicates that it did not perform such authentication, the caller must decide whether to cancel the context or continue without authentication.

The following table shows the various context requirements.

Type
Description
DELEGATE | Indicates that the server in the transport application should be allowed simple delegation rights, that is, impersonation of the client on the node at which the server is executing.
MUTUAL_AUTH | Indicates that both parties must authenticate the identity of the peer.
REPLAY_DETECT | Indicates that the context should be established to allow detection of replayed packets later through the message support functions, MakeSignature and VerifySignature. The REPLAY_DETECT context implies INTEGRITY.
SEQUENCE_DETECT | Indicates that the context should be established to allow detection of out-of-order delivery of packets later through the message support functions. The SEQUENCE_DETECT context implies INTEGRITY.
CONFIDENTIALITY | Indicates that the context should be established to protect data while in transit. This context type is reserved for future use.
USE_SESSION_KEY | Indicates that a new session key should be negotiated.
PROMPT_FOR_CREDS | Indicates that, the security package should prompt the user for the appropriate credentials to use, if possible and if the client is an interactive user.
USE_SUPPLIED_CREDS | Indicates that package-specific credential information is available in the input buffer. The security package should use these credentials to authenticate the connection.
ALLOCATE_MEMORY | Indicates that the security package should allocate the memory. The caller must eventually call the FreeContextBuffer function to free memory allocated by the security package.
USE_DCE_STYLE | Indicates that the caller expects a three transfer authentication transaction.
DATAGRAM | Indicates that datagram semantics should be used.
CONNECTION | Indicates that connection semantics should be used.
STREAM | Indicates that stream semantics should be used.
EXTENDED_ERROR | Indicates that if the context fails the application will generate an error reply message for the peer.
INTEGRITY | Indicates that buffer integrity can be verified, but no sequencing or reply detection is enabled.


Memory Use and Buffers

Memory is handled through a list of descriptors for the buffers being passed to the functions. Because certain protocols require access to an entire message, the entire message is available. To ensure integrity of the application, however, you can prohibit a package from modifying an area of a message.

The context functions use the SecBuffer and SecBufferDesc structures to pass memory buffers. The client creates an array of SecBuffer structures that references only the buffers that the application will be passing in to the package. The security package may indicate that it will be looking at only the security portion of a message, and that the SSPI client need not provide the other portions of the message. By passing only portions of a message instead of an entire message, performace improves.

The SecBufferDesc structure is a header that includes a pointer to the array of SecBuffer structures. The following code example shows how the server initializes an array of buffers when it calls the AcceptSecurityContext function. Note that the last buffer contains the opaque security token received by the client and that the SECBUFFER_READONLY flag is also set.

SecBuffer        Buffers[3];
SecBufferDesc    BufferDesc;
...
BufferDesc.ulVersion = SECBUFFER_VERSION;
BufferDesc.cBuffers = 3;
BufferDesc.pBuffers = &Buffers;
The application can then initialize the pointers and sizes in the buffer description to indicate where things may be found:
Buffers[0].cbBuffer = sizeof(Protocol_Header);
Buffers[0].BufferType = SECBUFFER_READONLY | SECBUFFER_DATA;
Buffers[0].pvBuffer = pHeader;

Buffers[1].cbBuffer = pHeader->MessageSize;
Buffers[1].BufferType = SECBUFFER_DATA;
Buffers[1].pvBuffer = pMessage;

Buffers[2].cbBuffer = pHeader->TrailerSize;
Buffers[2].BufferType = SECBUFFER_READONLY | SECBUFFER_TOKEN;
Buffers[2].pvBuffer = pSecurityTrailer;


Securing the Message Exchange

After a security context is established, the application can use the SSPI message support functions to transmit signed messages. The process of signing a message ensures that the message cannot be tampered with.

If an application wants to generate signed messages, the client must set the ISC_REQ_REPLAY_DETECT or ISC_REQ_SEQUENCE_DETECT flag of the context attribute argument when calling the InitializeSecurityContext function for the first time.

Signing messages requires that both the client and the server service providers establish a common session key that is used to sign messages on the sender’s side of the communication and verify messages on the receiver’s side of the communication. The algorithims used in message signatures are known only to the security package.

After an authenticated connection has been established, the client or server can pass the security context and a message to the MakeSignature function to generate a secure signature. The MakeSignature function generates a checksum of the message and also provides sequencing information to prevent the message from being modified in transit.

The following code example shows how to implement the MakeSignature function and how to append the signature to the message so that the receiver is able to extract it when received.

//
// Setup the Buffer Descriptors.
//
OutBufferDesc.ulVersion = 0;
OutBufferDesc.cBuffers = 2;
OutBufferDesc.pBuffers = &OutSecBuffer;

OutSecBuffer[0].cbBuffer = MessageLen;
OutSecBuffer[0].BufferType = SECBUFFER_DATA | SECBUFFER_READONLY;
OutSecBuffer[0].pvBuffer = Message;
OutSecBuffer[1].cbBuffer = SignatureLen;
OutSecBuffer[1].BufferType = SECBUFFER_EMPTY;
OutSecBuffer[1].pvBuffer = (Message + MessageLen); // just after the message
//
// Now call MakeSignature API to get it signed.
//
SecurityStatus = (*SecurityInterface->MakeSignature)(
              &SecurityContext,
0,
BufferDescriptor,
Sequence
);

The sender then uses the buffer descriptor, including the signature, to construct a message to send to the receiver. The receiver takes the message and disassembles it to recreate the buffer descriptor. The receiver then calls the VerifySignature function to verify that the message received is correct according to the information in the signature.

The following code example shows how to implement the VerifySignature function.

//
// Setup the Buffer Descriptors.
//
InBufferDesc.ulVersion = 0;
InBufferDesc.cBuffers = 2;
InBufferDesc.pBuffers = &InSecBuffer;

InSecBuffer[0].cbBuffer = MessageLen;
InSecBuffer[0].BufferType = SECBUFFER_DATA | SECBUFFER_READONLY;
InSecBuffer[0].pvBuffer = Message;
InSecBuffer[1].cbBuffer = SignatureLen;
InSecBuffer[1].BufferType = SECBUFFER_TOKEN;
InSecBuffer[1].pvBuffer = (Message + MessageLen); // just after the message
//
// Now call MakeSignature API to get it signed.
//
SecurityStatus = (*SecurityInterface->VerifySignature)(
              &SecurityContext,
BufferDescriptor,
Sequence,
&QualityOfProtection
);


Deleting a Security Context

To delete a security context after the client and the server have finished communicating, both the client and the server call the DeleteSecurityContext function with their respective context handles. The client should also call the FreeCredentialsHandle function when it has finished communicating with any server or has finished using the additional credentials passed to the AcquireCredentialsHandle function. The server should then call DeleteSecurityContext when it is ready to shut down, but before unloading the DLL.


Window and Control Styles

Window and control styles are attributes controlled by specific style flags. There are also extended styles that have their own set of flags.


Window and Message Box Styles

The following table describes window styles supported by Windows CE.

Window Styles
Basic Window Styles
 Description
WS_CHILD | Specifies a child window. This should not be changed after the window is created.
WS_POPUP | Specifies a pop-up window. This style should not be changed after the window is created.
WS_VISIBLE | Specifies a window that is initially visible. This style can be turned on and off to change window visibility.
WS_DISABLED | Specifies a window that is initially disabled. A disabled window cannot receive input from the user.
WS_CLIPCHILDREN | Excludes the area occupied by child windows when drawing occurs within the parent window. This style is used on the parent window. Windows CE windows always have the WS_CLIPCHILDREN style.
WS_CLIPSIBLINGS | Excludes the area occupied by sibling windows above a window.
WS_GROUP | Specifies the first control of a group of controls. This style is used primarily when creating dialog boxes. The group consists of this first control and all controls defined after it, up to the next control for which the WS_GROUP style is specified. Because the first control in each group often has the WS_TABSTOP style, a user can move from group to group.
WS_TABSTOP | Specifies a control that can receive the keyboard focus when the user presses the TAB key. This style is used primarily when creating controls in a dialog box. Pressing the TAB key changes the keyboard focus to the next control with the WS_TABSTOP style.
WS_EX_NOACTIVATE | Specifies that a window cannot be activated. If a child window has this style, tapping it does not cause its top-level parent to activate. Although a window that has this style will still receive stylus events, neither it nor its child windows can get the focus. This style is supported only on Windows CE.
WS_EX_NODRAG | Specifies a stationary window that cannot be dragged by its title bar. This style is supported only on Windows CE.
WS_EX_NOANIMATION | Prevents a window from showing animated exploding and imploding rectangles and from having a button on the taskbar. This style is supported only on Windows CE.
WS_EX_TOPMOST | Creates a window that will be placed and remain above all non-topmost windows. To add or remove this style, use the SetWindowPos function.
Non-Client Area Styles
 Description
WS_BORDER | Specifies a window with a thin-line border
WS_CAPTION | Specifies a window with a title bar and border
WS_DLGFRAME | Specifies a window with a dialog box border style. A window with this style cannot have a title bar.
WS_HSCROLL | Specifies a window with a horizontal scroll bar
WS_VSCROLL | Specifies a window with a vertical scroll bar
WS_OVERLAPPED | Specifies a window with the WS_BORDER and WS_CAPTION styles
WS_SYSMENU | Specifies a window with a window menu on its title bar. Use in conjunction with the WS_CAPTION style. Windows CE does not have a system menu, but you can use the WS_SYSMENU style to add the standard Close (X) button to a window title bar.
WS_EX_CAPTIONOKBTN | Includes an OK button in the title bar
WS_EX_CLIENTEDGE | Specifies a window with a border with a sunken edge
WS_EX_CONTEXTHELP | Includes a Help button (?) in the title bar of the window
WS_EX_DLGMODALFRAME | Specifies a window with a double border
WS_EX_OVERLAPPEDWINDOW | Combines the WS_EX_CLIENTEDGE and WS_EX_WINDOWEDGE styles
WS_EX_STATICEDGE | Specifies a window with a three-dimensional border style. This style should be used for items that do not accept user input.
WS_EX_WINDOWEDGE | Specifies a window border with a raised edge

The following table shows message box styles supported by Windows CE.

Message Box Styles
Button Styles
 Description
MB_ABORTRETRYIGNORE | The message box contains three buttons: Abort, Retry, and Ignore.
MB_OK | The message box contains one button: OK.
MB_OKCANCEL | The message box contains two buttons: OK and Cancel.
MB_RETRYCANCEL | The message box contains two buttons: Retry and Cancel.
MB_YESNO | The message box contains two buttons: Yes and No.
MB_YESNOCANCEL | The message box contains three buttons: Yes, No, and Cancel.
MB_DEFBUTTON1 | The first button is the default button. Note that the first button is always the default unless you specify MB_DEFBUTTON2.
MB_DEFBUTTON2 | The second button is the default button.
MB_DEFBUTTON3 | The third button is the default button.
Icon Styles
 Description
MB_ICONASTERISK
MB_ICONINFORMATION 		An icon consisting of a lowercase letter i in a circle appears in the message box.
MB_ICONEXCLAMATION
MB_ICONWARNING 		An exclamation-point icon appears in the message box.
MB_ICONERROR
MB_ICONHAND
MB_ICONSTOP 		A stop-sign icon appears in the message box.
MB_ICONQUESTION | A question-mark icon appears in the message box.
Window Styles
 Description
MB_APPLMODAL The user must respond to the message box before continuing work in the window identified by the hWnd parameter. However, the user can move to the windows of other applications and work in those windows.

Depending on the hierarchy of windows in the application, the user may be able to move to other windows within the application. All child windows of the message box’s parent window are automatically disabled, but pop-up windows are not.

MB_APPLMODAL is the default value. Windows CE does not support either MB_SYSTEMMODAL or MB_TASKMODAL.

MB_SETFOREGROUND | The message box becomes the foreground window.
MB_TOPMOST | The message box is created with the WS_EX_TOPMOST window style.


Control Styles

The following table shows window control styles supported by Windows CE.

Window Control Styles
Check Box Styles
 Description
BS_3STATE | Creates a check box in which the box can be unavailable as well as selected or cleared. Use the unavailable state to show that the state of the check box is not determined.
BS_AUTO3STATE | Creates a three-state check box in which the state cycles through selected, unavailable, and cleared each time the user selects the check box.
BS_AUTOCHECKBOX | Creates a check box in which the check state toggles between selected and cleared each time the user selects the check box.
BS_CHECKBOX | Creates a small, empty check box with a label displayed to the right of it. To display the text to the left of the check box, combine this flag with the BS_RIGHTBUTTON style.
BS_LEFT | Left-aligns the text in the button rectangle on the right side of the check box.
BS_RIGHT | Right-aligns text in the button rectangle on the right side of the check box.
BS_RIGHTBUTTON | Positions a check box square on the right side of the button rectangle.
WS_TABSTOP | Turns the control into a tab stop, which enables the user to select the control by tabbing through the controls in a dialog box.
Combo Box Styles
 Description
CBS_AUTOHSCROLL | Automatically scrolls the text in an edit control to the right when the user types a character at the end of the line. If this style is not set, only text that fits within the rectangular boundary is enabled.
CBS_DISABLENOSCROLL | Shows a disabled vertical scroll bar in the list box when the box does not contain enough items to scroll. Without this style, the scroll bar is hidden when the list box does not contain enough items.
CBS_DROPDOWN | Displays only the edit control by default. The user can display the list box by selecting an icon next to the edit control.
CBS_DROPDOWNLIST | Displays a static text field that displays the current selection in the list box.
CBS_LOWERCASE | Converts any uppercase characters typed into the edit control of a combo box to lowercase.
CBS_NOINTEGRALHEIGHT | Specifies that the combo box will be exactly the size specified by the application when it created the combo box. Usually, Windows CE sizes a combo box so that it does not display partial items.
CBS_OEMCONVERT | Converts text typed in the combo box edit control from the Windows CE character set to the OEM character set and then back to the Windows CE set. This style is most useful for combo boxes that contain file names. It applies only to combo boxes created with the CBS_DROPDOWN style.
CBS_SORT | Sorts strings typed into the list box
CBS_UPPERCASE | Converts any lowercase characters typed into the edit control of a combo box to uppercase
WS_TABSTOP | Turns control into a tab stop, which enables the user to select the control by tabbing through the controls in a dialog box
Edit Control Styles
 Description
ES_AUTOHSCROLL | Automatically scrolls text to the right by 10 characters when the user types a character at the end of the line. When the user presses the enter key, the control scrolls all text back to the zero position.
ES_AUTOVSCROLL | Scrolls text up one page when the user presses the ENTER key on the last line
ES_CENTER | Centers text in a multiline edit control
ES_COMBOBOX | Indicates that the edit control is part of a combo box
ES_LEFT | Left-aligns text
ES_LOWERCASE | Converts all characters to lowercase as they are typed into the edit control
ES_MULTILINE Designates a multiline edit control. The default is a single-line edit control

When the multiline edit control is in a dialog box, the default response to pressing the ENTER key is to activate the default button. To use the ENTER key as a carriage return, use the ES_WANTRETURN style.

When the multiline edit control is not in a dialog box and the ES_AUTOVSCROLL style is specified, the edit control shows as many lines as possible and scrolls vertically when the user presses the ENTER key. If you do not specify ES_AUTOVSCROLL, the edit control shows as many lines as possible and beeps if the user presses the ENTER key when no more lines can be displayed.

If you specify the ES_AUTOHSCROLL style, the multiline edit control automatically scrolls horizontally when the caret goes past the right edge of the control. To start a new line, the user must press the ENTER key. If you do not specify ES_AUTOHSCROLL, the control automatically wraps words to the beginning of the next line when necessary. A new line is also started if the user presses the ENTER key. The window size determines the position of the word wrap. If the window size changes, the word wrapping position changes and the text is redisplayed.

Multiline edit controls can have scroll bars. An edit control with scroll bars processes its own scroll bar messages. Note that edit controls without scroll bars scroll as described in the previous paragraphs and process any scroll messages sent by the parent window.
ES_NOHIDESEL | Negates the default behavior for an edit control. The default behavior hides the selection when the control loses the input focus and inverts the selection when the control receives the input focus. If you specify ES_NOHIDESEL, the selected text is inverted, even if the control does not have the focus.
ES_NUMBER | Accepts only digits to be typed into the edit control
ES_OEMCONVERT | Converts text typed in the edit control from the Windows CE character set to the OEM character set, and then converts it back to the Windows CE set. This style is most useful for edit controls that contain file names.
ES_PASSWORD | Displays an asterisk (*) for each character typed into the edit control. You can use the EM_SETPASSWORDCHAR message to change the displayed character.
ES_READONLY | Prevents the user from typing or editing text in the edit control
ES_RIGHT | Right-aligns text in a multiline edit control
ES_UPPERCASE | Converts all characters to uppercase as they are typed into the edit control
ES_WANTRETURN | Specifies that a carriage return be inserted when the user presses the ENTER key while typing text into a multiline edit control in a dialog box. If you do not specify this style, pressing the ENTER key has the same effect as pressing the dialog box’s default push button. This style has no effect on a single-line edit control.
WS_TABSTOP | Turns control into a tab stop, which enables the user to select the control by tabbing through the controls in a dialog box
WS_TABSTOP | Turns control into a tab stop, which enables the user to select the control by tabbing through the controls in a dialog box
List Box Styles
 Description
LBS_DISABLENOSCROLL | Shows a disabled vertical scroll bar for the list box when the box does not contain enough items to scroll. If you do not specify this style, the scroll bar is hidden when the list box does not contain enough items.
LBS_EXTENDEDSEL | Enables the user to select multiple items by using the SHIFT key and the mouse or hot keys
LBS_MULTICOLUMN | Specifies a multicolumn list box that the user scrolls horizontally. You set the width of the columns by using the LB_SETCOLUMNWIDTH message.
LBS_MULTIPLESEL | Turns string selection on or off each time a user taps or double-taps a string in the list box. A user can select any number of strings simultaneously.
LBS_NOINTEGRALHEIGHT | Specifies that the list box will be exactly the size specified by the application when it created the list box. Usually, Windows CE sizes a list box so that it does not display partial items.
LBS_NOREDRAW | Ensures the list box appearance is not automatically updated when changes are made. You can change this style by sending a WM_SETREDRAW message.
LBS_NOSEL | Specifies that the user can view list box strings, but cannot select them
LBS_NOTIFY | Notifies the parent window when the user taps or double-taps a string in the list box
LBS_SORT | Sorts strings in the list box alphabetically
LBS_STANDARD | Sorts strings in the list box alphabetically. The parent window receives an input message when the user taps or double-taps a string. The list box has borders on all sides.
LBS_USETABSTOPS | Enables a list box to recognize and expand tab characters when drawing its strings. The default tab positions are 32 dialog box units. A dialog box unit is equal to one-fourth of the current dialog box base-width unit. Windows CE calculates these units based on the height and width of the current system font.
LBS_WANTKEYBOARDINPUT | Specifies that the owner of the list box receives WM_VKEYTOITEM messages when the user presses a key and the list box has the input focus. This enables an application to perform special processing on the keyboard input.
WS_TABSTOP | Turns control into a tab stop, which enables the user to select the control by tabbing through the controls in a dialog box.
Push Button Styles
BS_BOTTOM | Places the text at the bottom of the button rectangle
BS_CENTER | Centers the text horizontally in the button rectangle
BS_DEFPUSHBUTTON | Creates a push button with a heavy black border. If the button is in a dialog box, the user can select the button by pressing the enter key, even when the button does not have the input focus. This style is useful for enabling the user to quickly select the most likely option, or default.
BS_LEFT | Left-aligns the text in the button rectangle
BS_NOTIFY | Enables a button to send BN_DBLCLK, BN_KILLFOCUS, and BN_SETFOCUS notification messages to its parent window. Note that the button sends the BN_CLICKED notification message regardless of whether it has this style.
BS_OWNERDRAW | Creates an owner-drawn button. The owner window receives a WM_MEASUREITEM message when the button is created and a WM_DRAWITEM message when a visual aspect of the button has changed.
BS_PUSHBUTTON | Creates a push button that posts a WM_COMMAND message to the owner window when the user clicks the button
BS_RIGHT | Right-aligns text in the button rectangle
BS_TOP | Places text at the top of the button rectangle
BS_VCENTER | Vertically centers text in the button rectangle
WS_TABSTOP | Turns the control into a tab stop, which enables the user to select the control by tabbing through the controls in a dialog box
Radio Button Styles
BS_AUTORADIOBUTTON | Creates a radio button that, when selected by a user, clears all other buttons in the same group
BS_RADIOBUTTON | Creates a small circle with a label displayed to the right of it. To display the text to the left of the circle, combine this flag with the BS_RIGHTBUTTON style.
BS_LEFT | Left-aligns the text in the button rectangle on the right side of the check box
BS_RIGHT | Right-aligns the text in the button rectangle on the right side of the check box
BS_RIGHTBUTTON | Positions a check box square on the right side of the button rectangle
WS_TABSTOP | Turns the control into a tab stop, which enables the user to select the control by tabbing through the controls in a dialog box
Scroll bar Styles
SBS_HORZ | Designates a horizontal scroll bar. If the SBS_TOPALIGN style is not specified, the scroll bar has the height, width, and position specified by the parameters of CreateWindow.
SBS_VERT | Designates a vertical scroll bar. If you do not specify the SBS_LEFTALIGN style, the scroll bar has the height, width, and position specified by the parameters of CreateWindow.
Static Control Styles
SS_BITMAP | Specifies that a bitmap will be displayed in the static control. The text is the name of a bitmap defined elsewhere in the resource file, not a file name. The style ignores the nWidth and nHeight parameters; the control automatically sizes itself to accommodate the bitmap.
SS_CENTER | Specifies a simple rectangle and centers the error value text in the rectangle. Windows CE formats the text before display. The control automatically wraps words that extend past the end of a line to the beginning of the next centered line.
SS_CENTERIMAGE | Specifies that the midpoint of a static control with the SS_BITMAP style will remain fixed when you resize the control. The four sides are adjusted to accommodate a new bitmap. If the bitmap is smaller than the control’s client area, the rest of the client area is filled with the color of the pixel in the upper-left corner of the bitmap.
SS_ICON | Specifies that an icon will be displayed in the static control. The text is the name of an icon defined elsewhere in the resource file, not a file name. The style ignores the nWidth and nHeight parameters; the icon automatically sizes itself.
SS_LEFT | Specifies a rectangle and left-aligns the text in the rectangle. Windows CE formats the text before display. The control automatically wraps words that extend past the end of a line to the beginning of the next left-aligned line.
SS_LEFTNOWORDWRAP | Specifies a rectangle and left-aligns the text in the rectangle. Tabs are expanded, but words are not wrapped. Text that extends past the end of a line is clipped.
SS_NOPREFIX Prevents interpretation of any ampersand (&) characters in the control’s text as accelerator prefix characters

An application can combine SS_NOPREFIX with other styles by using the bitwise OR (|) operator. This can be useful when file names or other strings that may contain an ampersand (&) must be displayed within a static control in a dialog box.
SS_NOTIFY | Sends the parent window the STN_CLICKED notification when the user clicks the control
SS_RIGHT | Specifies a rectangle and right-aligns the specified text in the rectangle. Windows CE formats the text before display. The control automatically wraps words that extend past the end of a line to the beginning of the next right-aligned line.

The following table shows common control styles supported by Windows CE.

Common Control Styles
Basic Common Control Styles
 Description
CCS_ADJUSTABLE | Enables a toolbar’s built-in customization features, which enable the user to drag a button to a new position or to remove a button by dragging it off the toolbar. In addition, the user can double-click the toolbar to display the Customize Toolbar dialog box, which enables the user to add, delete, and rearrange toolbar buttons.
CCS_BOTTOM | Causes the control to position itself at the bottom of the parent window’s client area and sets the width of the control to be the same as the parent window’s width. Status windows have this style by default.
CCS_NODIVIDER | Prevents a 2-pixel highlight from being drawn at the top of the control
CCS_NOMOVEY | Causes the control to resize and move itself horizontally, but not vertically, in response to a WM_SIZE message. Header windows have this style by default. This style does not apply if your control has the CCS_NORESIZE style.
CCS_NOPARENTALIGN | Prevents the control from automatically moving to the top or bottom of the parent window. Instead, the control keeps its position within the parent window despite changes to the size of the parent. If the application also uses the CCS_TOP or CCS_BOTTOM styles, it adjusts the height to the default, but does not change the position and width of the control.
CCS_NORESIZE | Prevents the control from using the default width and height when setting its initial size or a new size. Instead, the control uses the width and height specified in the request for creation or sizing.
CCS_TOP | Causes the control to position itself at the top of the parent window client area and matches the width of the control to the width of the parent window. Toolbars have this style by default.
CCS_LEFT | Causes the control to display vertically on the left side of the parent window
CCS_RIGHT | Causes the control to display vertically on the right side of the parent window
CCS_NOMOVEX | Causes the control to resize and move itself vertically, but not horizontally, in response to a WM_SIZE message. This message does not apply if your control has the CCS_NORESIZE style.
CCS_VERT | Causes the control to display vertically
Date and Time Picker Styles
 Description
DTS_APPCANPARSE | Enables the owner to parse user input. When a Date Time Picker control has this style, a user can make changes within the client area of the control by pressing the F2 key. The control sends a DTN_USERSTRING notification message when the user is finished editing.
DTS_LONGDATEFORMAT | Displays the date in long format. The default format string for this style is defined by LOCALE_SLONGDATEFORMAT, which produces output like “Friday, April 19, 1998.”
DTS_SHOWNONE | Enables the control to accept “no date” as a valid selection state. This state can be set with the DTM_SETSYSTEMTIME message or verified with the DTM_GETSYSTEMTIME message.
DTS_SHORTDATEFORMAT | Displays the date in short format. The default format string for this style is defined by LOCALE_SSHORTDATE, which produces output like “4/19/98.”
DTS_TIMEFORMAT | Displays the time. The default format string for this style is defined by LOCALE_STIMEFORMAT, which produces output like “5:31:42 PM.” An up-down control is placed to the right of the DTP control to modify time values.
DTS_UPDOWN | Places an up-down control to the right of a DTP control to modify time values. This style can be used instead of the drop-down month calendar, which is the default style.
Header Control Styles
 Description
HDS_BUTTONS | Causes each header item to look and behave like a button. This style is useful if an application carries out a task when the user clicks an item in the header control.
HDS_DRAGDROP | Enables drag-and-drop reordering of header items
HDS_FULLDRAG | Causes the header control to display column contents even while a user resizes a column
HDS_HIDDEN | Creates a header control that you can hide by setting its height to zero. This style is useful when you use the control as an information container instead of a visual control.
HDS_HORZ | Creates a horizontal header control
List View Styles
 Description
LVS_ALIGNLEFT | Specifies that items are left-aligned in icon view and small icon view
LVS_ALIGNTOP | Specifies that items are aligned with the top of the list view control in icon view and small icon view
LVS_AUTOARRANGE | Specifies that icons automatically remain arranged in icon view and small icon view
LVS_EDITLABELS | Enables item text to be edited in place. The parent window must process the LVN_ENDLABELEDIT notification message.
LVS_ICON | Specifies icon view
LVS_LIST | Specifies list view
LVS_NOCOLUMNHEADER | Specifies that no column header is displayed in report view, which is the default view
LVS_NOLABELWRAP | Displays item text on a single line in icon view. By default, item text may wrap in icon view.
LVS_NOSCROLL | Disables scrolling, so all items must be displayed within the client area
LVS_NOSORTHEADER | Specifies that column headers do not work like buttons. This style is useful if clicking a column header in report view does not carry out any action, such as sorting.
LVS_OWNERDATA | Creates a virtual list view control
LVS_OWNERDRAWFIXED | Enables the owner window to paint items in report view. The list view control sends a WM_DRAWITEM message to paint each item; it does not send separate messages for each subitem. The itemData member of the DRAWITEMSTRUCT structure contains the item data for the specified list view item.
LVS_REPORT | Specifies report view
LVS_SHAREIMAGELISTS | Specifies that the control does not destroy the image lists assigned to it when it is destroyed. This style enables the same image lists to be used with multiple list view controls.
LVS_SHOWSELALWAYS | Always shows the selection highlighted, even if the control is not activated
LVS_SINGLESEL | Enables only one item to be selected at a time. By default, multiple items can be selected.
LVS_SMALLICON | Specifies small icon view
LVS_SORTASCENDING | Sorts items based on item text in ascending order
LVS_SORTDESCENDING | Sorts items based on item text in descending order
LVS_EX_CHECKBOXES | Enables items in a list view control to be displayed as check boxes. This style uses item state images to produce the check box effect.
LVS_EX_FULLROWSELECT | Specifies that when an item is selected, the item and all its subitems are highlighted. This style is available only in conjunction with the LVS_REPORT style.
LVS_EX_GRIDLINES | Displays gridlines around items and subitems. This style is available only in conjunction with the LVS_REPORT style.
LVS_EX_HEADERDRAGDROP | Enables drag-and-drop reordering of columns in a list view control. This style is only available to list view controls that use the LVS_REPORT style.
LVS_EX_SUBITEMIMAGES | Enables images to be displayed for subitems. This style is available only in conjunction with the LVS_REPORT style.
Month Calendar Control Styles
Description
MCS_DAYSTATE | Specifies that the month calendar will send MCN_GETDAYSTATE notifications to request information about which days should be displayed in bold.
MCS_MULTISELECT | Enables the user to select a range of dates. By default, the maximum range is one week. You can change the maximum selectable range using the MCM_SETMAXSELCOUNT message.
MCS_NOTODAY | Creates a month calendar that does not display a Today selection
MCS_NOTODAYCIRCLE | Creates a month calendar that does not circle the current date
MCS_WEEKNUMBERS | Displays the week number, from 1 through 52, to the left of each week in the calendar
Progress Bar Styles
 Description
PBS_SMOOTH | Displays progress status in a smooth scrolling bar instead of the default segmented bar
PBS_VERTICAL | Displays progress status vertically, from bottom to top
Rebar Styles
 Description
CCS_VERT | Causes the control to appear vertically at the left side of the parent window
RBS_AUTOSIZE | Specifies that the layout of a band will automatically change when the size or position of its control changes. When the layout changes, the control sends an RBN_AUTOSIZE notification.
RBS_BANDBORDERS | Displays narrow lines to separate adjacent bands
RBS_FIXEDORDER | Displays multiple bands in the same order at all times. A user can move bands to different rows, but the band order is static.
RBBS_NOGRIPPER | Creates a rebar band that displays no gripper. This style applies to individual bands, not to the entire rebar. Windows CE is the only Windows-based OS that supports the RBS_NOGRIPPER style for rebar controls.
RBS_SMARTLABELS | Displays the icon for a band that has one only when the band is minimized. If a band has a text label, the label is displayed only when the band is in its restored state or in its maximized state. Windows CE is the only Windows-based OS that supports the RBS_SMARTLABELS style for rebar controls.
RBS_VARHEIGHT | Displays a band at the minimum required height, when possible. Without this style, the command bands control displays all bands at the same height, using the height of the tallest visible band to determine the height of other bands.
RBS_VERTICALGRIPPER | Displays the size grip vertically, instead of horizontally, in a vertical command bands control. This style is ignored for command bands controls that do not have the CCS_VERT style.
Tab Control Styles
 Description
TCS_BOTTOM | Displays the tabs at the bottom of the control. If the TCS_VERTICAL style is also specified, this style is interpreted as TCS_RIGHT.
TCS_BUTTONS | Displays all tabs as buttons with no border drawn around the display area
TCS_FIXEDWIDTH | Specifies that all tabs are the same width. You can not combine this style with the TCS_RIGHTJUSTIFY style.
TCS_FLATBUTTONS | Changes the appearance of a selected tab to indented while other tabs appear to be on the same plane as the background. This style only applies to tab controls that have the TCS_BUTTONS style.
TCS_FLIP | Flips all tabs from top to bottom or left to right
TCS_FOCUSNEVER | Creates a tab control that never receives the input focus
TCS_FOCUSONBUTTONDOWN | Specifies that a tab which, when selected, receives the input focus
TCS_FORCEICONLEFT | Aligns an icon with the left edge of a fixed-width tab. This style can only be used with the TCS_FIXEDWIDTH style.
TCS_FORCELABELLEFT | Aligns a label with the left edge of a fixed-width tab; that is, it displays the label immediately to the right of the icon instead of centering it. This style can only be used with the TCS_FIXEDWIDTH style, and it implies the TCS_FORCEICONLEFT style.
TCS_MULTILINE | Displays multiple rows of tabs, if necessary, so that all tabs are visible at once
TCS_MULTISELECT | Specifies that multiple tabs can be selected by holding down CTRL when selecting a tab. This style only applies to tabs that have the TCS_BUTTONS style.
TCS_OWNERDRAWFIXED | Specifies that the parent window is responsible for drawing tabs
TCS_RAGGEDRIGHT | Leaves a ragged right edge by not stretching a row of tabs to fill the entire width of the control. This style is the default.
TCS_RIGHT | Displays multiple tabs vertically on the right side of controls that use the TCS_VERTICAL style. If the TCS_VERTICAL style is not specified, this style is interpreted as TCS_BOTTOM.
TCS_RIGHTJUSTIFY | Increases the width of each tab, if necessary, so that each row of tabs fills the entire width of the tab control. This style is valid only when used with the TCS_MULTILINE style.
TCS_SCROLLOPPOSITE | Specifies that unused tabs move to the opposite side of the control when a new tab is selected
TCS_SINGLELINE | Displays only one row of tabs. The user can scroll to see more tabs, if necessary. This style is the default.
TCS_VERTICAL | Displays multiple tabs vertically on the left side of the control. This style is valid only when used with the TCS_MULTILINE style. To make tabs appear on the right side of the control, combine this style with the TCS_RIGHT style.
Toolbar Styles
 Description
TBSTYLE_CUSTOMERASE | Creates a toolbar that generates NM_CUSTOMDRAW notification messages when it processes WM_ERASEBKGND messages.
TBSTYLE_FLAT | Creates a flat toolbar, in which both the toolbar and the buttons are transparent. Button text appears under button bitmaps.
TBSTYLE_LIST | Places button text to the right of button bitmaps. This style can only be used with the TBSTYLE_FLAT style. In Windows CE, the TBSTYLE_LIST style creates a toolbar with variable width buttons. If you want to use the TBSTYLE_LIST style with fixed width buttons, you can override the default behavior by sending a TB_SETBUTTONSIZE or TB_SETBUTTONWIDTH message.
TBSTYLE_TOOLTIPS | Creates a tooltip control that an application can use to display descriptive text for the buttons in the toolbar.
TBSTYLE_TRANSPARENT | Creates a transparent toolbar, in which the toolbar is transparent, but the buttons are not. Button text appears under button bitmaps.
TBSTYLE_WRAPABLE | Creates a toolbar that can have multiple rows of buttons. Toolbar buttons can wrap to the next line when the toolbar becomes too narrow to include all buttons on the same line. Wrapping occurs on separation and non-group boundaries.
TBSTYLE_BUTTON | Creates a toolbar button that looks like a standard Windows CE push button
TBSTYLE_CHECK | Creates a button that toggles between the pressed and not pressed states each time the user clicks it. The button has a different background color when it is in the pressed state.
TBSTYLE_CHECKGROUP | Creates a check button that stays pressed until another button in the group is pressed
TBSTYLE_GROUP | Creates a button that stays pressed until another button in the group is pressed
TBSTYLE_AUTOSIZE | Calculates a button width based on the text of the button, not on the size of the image
TBSTYLE_DROPDOWN | Creates a drop-down list button
TBSTYLE_SEP | Creates a separator, which provides a small gap between button groups. A button that has this style does not receive user input.
Tree View Styles
Description
TVS_CHECKBOXES | Enables items in a tree view control to be displayed as check boxes. This style uses item state images to produce the check box effect.
TVS_DISABLEDRAGDROP | Prevents the tree view control from sending TVN_BEGINDRAG notification messages
TVS_EDITLABELS | Enables the user to edit the labels of tree view items
TVS_HASBUTTONS | Displays plus (+) and minus (-) buttons next to parent items. The user taps the buttons to expand or collapse a parent item’s list of child items. To include buttons with items at the root of the tree view, you must also specify the TVS_LINESATROOT style.
TVS_HASLINES | Uses lines to show the hierarchy of items
TVS_LINESATROOT | Uses lines to link items at the root of the tree view control. This value is ignored if TVS_HASLINES is not also specified.
TVS_SHOWSELALWAYS | Uses the system highlight colors to draw the selected item
TVS_SINGLESEL | Specifies that when a new tree view item is selected, the selected item will automatically expand and the previously selected item will collapse.
Up-Down Control Styles
Description
UDS_ALIGNLEFT | Positions the up-down control next to the left edge of the buddy window. The buddy window is moved to the right and its width is decreased to accommodate the width of the up-down control.
UDS_ALIGNRIGHT | Positions the up-down control next to the right edge of the buddy window. The width of the buddy window is decreased to accommodate the width of the up-down control.
UDS_ARROWKEYS | Causes the up-down control to process the UP ARROW and DOWN ARROW keys on the keyboard
UDS_AUTOBUDDY | Automatically elects the previous window in the z-order as the up-down control’s buddy window. In Windows CE, the window must be an edit control.
UDS_HORZ | Causes the up-down control’s arrows to point left and right instead of up and down
UDS_NOTHOUSANDS | Refrains from inserting a thousands separator between every three decimal positions
UDS_SETBUDDYINT | Causes the up-down control to set the text of the buddy window, using the WM_SETTEXT message, when the position changes. The text consists of the position formatted as a decimal or hexadecimal string.
UDS_WRAP | Causes the position to wrap if it is incremented or decremented beyond the end or beginning of the range


A

ACCEL data structure
A data structure that defines an accelerator key used in an accelerator table.
accelerator table
An array of ACCEL data structures, each of which defines an accelerator.
Action button
A hardware navigation control on a Palm-size PC that functions like the enter key on a keyboard.
Active Channel
A Web site that has been enabled for Webcasting to information-receiving applications.
Active Desktop
A technology delivered in Microsoft Internet Explorer and Microsoft Pocket Internet Explorer that allows you to include HTML documents, ActiveX controls, and Java applets on your desktop.
active notification
The state of a user notification from the time the user is notified until the user handles the event. See also user notification.
Active Server Pages (ASP)
An open application environment in which HTML pages, scripts, and ActiveX components are combined to create Web-based applications.
active window
In an environment capable of displaying multiple on-screen windows, the window containing the display or document that will be affected by current cursor movements, commands, and text entry. See also graphical user interface. Compare inactive window.
ActiveX
A set of technologies that enable software components to interact with one another in a networked environment, regardless of the language in which the components were created. ActiveX, which was developed as a proposed standard by Microsoft in the mid 1990s and is currently administered by the Open Group, is built on Microsoft's Component Object Model (COM). Currently, ActiveX is used primarily to develop interactive content for the World Wide Web, although it can be used in desktop applications and other applications. ActiveX controls can be embedded in Web pages to produce animation and other multimedia effects, interactive objects, and sophisticated applications. See also COM.
ActiveX client
An application or tool that calls an ActiveX object.
ActiveX object
An exposed object of the Component Object Model (COM).
ADC
See analog-to-digital converter.
address card
The fundamental unit of record in the Contacts database. Each address card contains information about an individual, such as name and address.
Address Resolution Protocol (ARP)
A TCP/IP protocol for determining the hardware address, or physical address, of a node on a local area network connected to the Internet, when only the IP address, or logical address, is known. An ARP request is sent to the network, and the node that has the IP address responds with its hardware address. Although ARP technically refers only to finding the hardware address, and RARP, Reversed ARP, refers to the reverse procedure, ARP is commonly used for both senses. See also IP address, TCP/IP.
American National Standards Institute (ANSI)
A voluntary, nonprofit organization of U.S. business and industry groups formed in 1918 for the development of trade and communication standards. ANSI is the American representative of the International Standards Organization and has developed recommendations for the use of programming languages including FORTRAN, C, and COBOL.
American Standard Code for Information Exchange (ASCII)
A coding scheme using 7 or 8 bits that assigns numeric values to up to 256 characters, including letters, numerals, punctuation marks, control characters, and other symbols. ASCII was developed in 1968 to standardize data transmission among disparate hardware and software systems and is built into most minicomputers and all personal computers.
analog-to-digital converter (ADC)
A device that converts an analog signal, such as sound or voltage, to binary code for use by a computer.
annunciator
An icon placed onto the taskbar to indicate that a user notification is active. Although taskbars can contain multiple annunciator icons for different applications, only one instance of an icon for any given application is displayed at one time.
ANSI
See American National Standards Institute.
apartment model
A threading model that can be used only on the thread that created it. See free threading model and single threading model.
API
See application programming interface.
application-defined message
A message created by an application to be used by its own windows or to communicate with windows in other processes. If an application creates its own messages, the window procedure that receives the message must interpret it and provide the appropriate processing.
application notification
An application notification starts an application at a specified time or when a system event occurs. When an application starts as the result of a notification, the system specifies a command line parameter that identifies the event that has occurred.
application programming interface (API)
A set of routines used by an application to direct the performance of procedures by a computer’s operating system. For computers running a graphical user interface, an API manages an application’s windows, icons, menus, and dialog boxes.
application-specific integrated circuit (ASIC)
An integrated circuit designed to perform a particular function by defining the interconnection of a set of basic circuit-building blocks drawn from a library provided by the circuit manufacturer.
application switch
A hardware navigation control intended to launch or reactivate software applications.
ASCII
See American Standard Code for Information Interchange.
ASIC
See application-specific integrated circuit.
ASP
See Active Server Pages.
asynchronous operation
1. A process in a multitasking system whose execution can proceed independently, or in the background. Other processes may be started before the asynchronous process has finished. 2. A data transmission method that allows characters to be sent at irregular intervals over a line by preceding each character with a start bit and following it with a stop bit. Compare synchronous operation.
authentication
1. The process of verifying that a message comes from its stated source. 2. The process of verifying the identity or access level of a user, computer, or application.
Automation
A technology based on the Component Object Model (COM), that enables interoperability among ActiveX components, including OLE components. Formerly referred to as OLE Automation. See also OLE.


B

backlight
A light source for a back-lit display.
backup authority
A trusted application running on a secure computer used as a storage medium.
bandwidth
1. The difference between the highest and lowest frequencies that an analog communications system can pass. For example, a telephone accommodates a bandwidth of 3000 Hz, which is the difference between the lowest (300 Hz) and highest (3300 Hz) frequencies it can carry. 2. The data transfer capacity of a digital communications system.
bidirectional parallel port
An interface that supports two-way parallel communications between a device and a computer.
binary image builder file (.bib)
A file used by the Windows CE ROM image builder tool to determine which modules and files to combine when forming the ROM image, and where to place the modules in memory.
binary large object (BLOB)
1. A large piece of data, such as a bitmap, characterized by large field values, an unpredictable table size, and data that is formless from the perspective of an application. 2. A keyword that designates the BLOB structure that contains information about a block of data.
bit block transfer (bitblt)
The process of copying the bits that constitute a bitmap from one device context to another. For example, a bit block transfer can be used to move a bitmap stored in memory to the screen for display. The bits can also be altered during a bit block transfer. As a result, light and dark portions of an image can be reversed. Successive displays can thus be used to change the appearance of an image or to move it around on the screen.
bitmap
A data structure in memory that represents information in the form of a collection of individual bits. A bitmap is used to represent a bit image. Another use of a bit map in some systems is the representation of the blocks of storage on a disk, indicating whether each block is free (0) or in use (1).
blink time
The elapsed time, in milliseconds, required to invert the caret display. This value is half of the flash time.
BLOB
See binary large object.
block cipher mode
An encryption scheme in which data is encrypted one block at a time.
block mode
A synchronous method of calling the CeRapiInvoke function by storing input parameters and output data in a single buffer.
boot loader
An application that is automatically run when a computer is switched on (booted). After first performing a few basic hardware tests, the bootstrap loader loads and passes control to a larger loader application, which then typically loads the operating system. The bootstrap loader normally resides in the computer's read-only memory (ROM).
brush
A tool used in painting applications to sketch or fill in areas of a drawing with the color and pattern currently in use. Painting applications that offer a variety of brush shapes can produce brushstrokes of varying width and, in some cases, shadowing or calligraphic effects.
build environment
The state of the development workstation and the directory structure when an application build begins.
build window
See command prompt window.
built-in device driver
See native device driver.


C

cabinet file
A self-contained file with a .cab extension used for application installation and setup. In a cabinet file, multiple files are compressed into one file. They are commonly found on Microsoft software distribution disks.
cache
A special memory subsystem in which frequently used data values are duplicated for quick access. A memory cache stores the contents of frequently accessed RAM locations and the addresses where this data is stored. When the processor references an address in memory, the cache checks to see whether it holds that address. If it does hold the address, the data is returned to the processor; if it does not hold the address, a regular memory access occurs. A cache is useful when RAM accesses are slow compared with the microprocessor speed, because cache memory is always faster than main RAM memory.
callback function
A function that receives messages from the operating system. Callback functions are application-defined.
caret
A flashing line, block, or bitmap that marks the location of the insertion point in a window’s client area.
cascading menu
A hierarchical graphical menu system in which a side menu of subcategories is displayed when the pointer is placed on the main category.
CDF
See Channel Definition Format.
central processing unit (CPU)
The computational and control unit of a computer. The CPU is the device that interprets and executes instructions. It has the ability to fetch, decode, and execute instructions and to transfer information to and from other resources over the computer’s main data-transfer path, the bus. By definition, the CPU is the chip that functions as the “brain” of a computer. In some instances, however, the term encompasses both the processor and the computer’s memory or, even more broadly, the main computer console, as opposed to peripheral equipment.
certificate
A packet of data containing a public key and identification information. Every certificate is created and signed by certification authorities.
Certification Authority (CA)
An entity that attests to the identity of a person or an organization. The Certification Authority’s primary function is to verify the identity of entities and issue digital certificates attesting to that identity.
Cesh
The Windows CE Debug Shell Tool (Cesh.exe). This shell enables you to transfer an operating system image from the development workstation to the target platform and provides you with a set of commands to assist in debugging processes running on the target platform.
channel
A subscription to a Web site that conforms to the Channel Definition Format.
Channel Definition Format (CDF)
A specification developed by Microsoft and presented to the World Wide Web Consortium (W3C) that allows applications to send Web pages to users. Once a user subscribes to a CDF channel, any software that supports the CDF format automatically receives any new content posted on the channel’s Web server. The default client subscription application for Internet channel broadcasting in Broadcast Architecture stores subscription information as .cdf files.
channel script
An application written in HTML that uses Visual Basic Script, JScript, Java Script, and other scripting languages to specify the layout and behavior of a channel.
channel synchronization
The process of first downloading Mobile Channels content into a cache using the standard Internet Explorer 4.0 channel retrieval mechanism and then transferring it onto a Windows CE-based device. Channel synchronization makes it possible for users to access Mobile Channels using either a Windows CE-based device without a radio module or a Windows-based desktop computer when the device is not readily available.
check box
An interactive control found in graphical user interfaces. Check boxes are used to enable or disable one or more features or options from a set. When an option is selected, an x or a check mark appears in the box.
child window
A window that has the WS_CHILD style. A child window always appears within the client area of its parent window.
chord
A combination of navigation controls used to perform a defined function. A chord is functionally similar to a keyboard accelerator. For example, on some Palm-size PCs, pressing and holding an Action button and then pressing an Exit button toggles the backlight.
CIFS
See Common Internet File System.
CIFS redirector
A module through which one computer gains access to another. Its function is to reestablish disrupted connections and to package and send remote file-system requests to host targets.
cipher mode
A method used to encrypt data.
ciphertext
Data that has been encrypted.
class identifier (CLSID)
A universally unique identifier (UUID) that identifies a type of Component Object Model (COM) object. Each type of COM object item has its CLSID in the registry so that it can be loaded and used by other applications. For example, a spreadsheet may create worksheet items, chart items, and macrosheet items. Each of these item types has its own CLSID that uniquely identifies it to the system.
client
1. In object-oriented programming, a member of a class (group) that uses the services of another class to which it is not related. 2. A process, such as an application or task, that requests a service provided by another application. For example, a word processor that calls on a sort routine built into another application. The client process uses the requested service without having to know any working details about the other application or the service itself. 3. On a local area network or the Internet, a computer that accesses shared network resources provided by another computer, called a server.
client area
The client area is the portion of a window where the application displays output, such as text or graphics. Also called a client rectangle.
client coordinate
A coordinate that is relative to the upper-left corner of a window’s client area.
clipping region
A subregion of the client area to which output is restricted. Clipping is used in Windows CE in a variety of ways. For example, word processing and spreadsheet applications clip keyboard input to keep it from appearing in the margins of a page or spreadsheet.
CLSID
See class identifier.
cold boot
A startup process that begins with turning on the computer’s power. Typically, a cold boot involves some basic hardware checking by the system, after which the operating system is loaded from disk into memory. Compare warm boot.
COM
See Component Object Model.
COM class
The definition of an object in code. In COM, class refers to the general object definition, whereas in C++, the class of an object is a data type.
COM object
A programming structure that includes both data and functionality. A COM object is defined and allocated as a single unit. The only public access to a COM object is through the programming structure’s interfaces. At a minimum, a COM object must support the IUnknown interface, which maintains the object’s existence while it is being used and provides access to the object’s other interfaces.
COM port
Short for communications port, the logical address assigned by MS-DOS versions 3.3 and later, and Microsoft Windows to each of the four serial ports on an IBM personal computer or a PC-compatible. COM ports also have come to be known as the actual serial ports on a computer’s CPU where peripherals, such as printers, scanners, and external modems, are plugged in.
combo box
A control that combines an edit control with a list box. This allows the user to type in an entry or choose one from the list.
command band
A rebar control with a fixed band at the top that contains a toolbar with a Close (X) button, an OK button, and optionally, a Help (?) button in the upper-right corner.
command bar
A control window that can contain buttons, combo boxes, and menu bars. Windows CE-based applications can use a command bar rather than a separate menu and toolbar to efficiently utilize available screen space.
command prompt build window
A development workstation command prompt window from which the embedded toolkit user has run the Wince.bat tool. Also called build window.
common control
A standardized child window that an application uses in conjunction with another window to perform input/output tasks. A common control enables users to view and organize information and to set or change attributes and properties. Most common controls send the WM_NOTIFY message.
Common Internet File System (CIFS)
A standard proposed by Microsoft that would compete directly with Sun Microsystems’ Web Network File System. A system of file sharing of internet or intranet files.
Compact Flash
A group of related technologies for providing long-term storage through various types of non-volatile memory.
component
A subset of the Windows CE operating system. Windows CE is structured as a collection of modules that are subdivided into smaller components. Each module and component is a self-contained subset of the Windows CE operating system that can be used to construct a customized operating system for a particular device.
Component Object Model (COM)
An open architecture for cross-platform development of client/server applications. It is based on object-oriented technology as agreed upon by Digital Equipment Corporation and Microsoft Corporation. COM defines the interface, similar to an abstract base class, IUnknown, from which all COM-compatible classes are derived.
compound file
A number of individual files bound together in one physical file where each individual file can be accessed as if it were a single physical file.
Contacts database
A collection of names, addresses, telephone numbers, and other information stored on a Windows CE device by the Contacts application. The database is divided into a set of records called address cards. The database contains any number of address cards, limited only by the amount of memory available on the device.
# K container
A network resource that contains other resources.
continuous resistive touch panel
See touch screen.
control
A standardized child window on the screen that can be manipulated by the user to perform an action or display information. The most common controls are buttons, which allow the user to select options, and scroll bars, which allow the user to move through a document or position text in a window.
control identifier
A value that uniquely identifies a control.
control style
A value, similar to a window style, that specifies the appearance and behavior of a control. The window procedure for the control uses the style to determine how to draw the control and process input.
CPU
See central processing unit.
credential
Data used by a principal to establish the identity of the principal, such as a password or user name.
cryptographic service provider (CSP)
An independent module that performs cryptographic operations, such as creating and destroying keys. A cryptographic service provider consists of, at a minimum, a dynamic-link library (DLL) and a signature file.
cursor
A small bitmap whose location on the screen is controlled by a pointing device, such as a mouse, pen, or trackball. Some Windows CE-based platforms only support the wait cursor—the spinning hourglass.


D

database synchronization
The process of bringing two separate copies of a database into agreement.
database system application programming interface
A set of functions that enable you to create and manipulate Windows CE databases. Each database consists of an arbitrary number of records, and each record consists of at least one property.
database type identifier
A user-specified token, or number, that is attached to a database. The token can be used to identify related databases by associating the same value, or related values, with each database.
data link
A connection between any two devices capable of sending and receiving information, such as a computer and a printer or a main computer and a terminal. Sometimes the term is extended to include equipment, such as a modem, that enables transmission and receiving. Such devices follow protocols that govern data transmission.
date and time picker control
A control that displays information about dates and times, and provides users with an easy way to modify this information.
DCC
See direct cable connection.
DDB
See device-dependent bitmap.
DDE
See dynamic data exchange.
DDI
See device driver interface.
DDK
See device driver kit.
dead key
A key used with another key to create an accented character. A dead key, when pressed, produces no visible character, but indicates that the accent mark it represents is to be combined with the character produced by the next letter key pressed.
decryption
The process of returning encrypted data to its original form.
derived session key
A session key created by an application as needed. Before creating a derived session key, an application prompts the user for a password.
deserialize
The process of converting a series of bytes back into an object. See serialize.
desktop
An on-screen work area that uses icons and menus to simulate the top of a desk. Its intent is to make a computer easier to use by enabling users to move pictures of objects and to start and stop tasks in much the same way as they would if they were working on a physical desktop.
desktop connectivity
The services required to connect a Windows CE-based device to a desktop computer.
development workstation
The PC-based computer on which you install Windows CE development toolkits and develop software for your Windows CE-based platform.
device
1. A generic term for a computer subsystem. Printers, serial ports, and disk drives are often referred to as devices; such subsystems frequently require their own controlling software, called device drivers. 2. A hardware feature that can—or must—be part of the target platform. For example, a built-in device could be a low-battery notification LED, while a PC Card modem is an installable device.
device context
A GDI structure containing information that governs the display of text and graphics on a particular output device. A device context stores, retrieves, and modifies the attributes of graphic objects and specifies graphic modes. The graphic objects stored in a device context include a pen for line drawing, a brush for painting and filling, a font for text output, a bitmap for copying or scrolling, a palette for defining the available colors, and a region for clipping.
device-dependent bitmap (DDB)
An array of bits that can only be used with a particular display or printer.
device driver
A software component that permits a computer system to communicate with a device. In most cases, the driver also manipulates the hardware in order to transmit the data to the device. However, device drivers associated with application packages typically perform only the data translation; these higher-level drivers then rely on lower-level drivers to actually send the data to the device. Many devices will not work properly—if at all—without the correct device drivers installed in the system.
device driver interface (DDI)
1. The interface between applications and the device drivers. 2. A set of functions implemented in the model device driver and called by the Graphics, Windowing, and Events Subsystem (GWES).
Device Driver Kit (DDK)
A set of tools and libraries that enable programmers to write Windows-based software used to run hardware devices such as printers.
Device Driver Test Kit (DDTK)
A set of tools and libraries that enable you to test the porting of your device drivers to the Windows CE operating system.
device-independent bitmap (DIB)
An array of bits combined with several structures that specify the width and height of the bitmap image (in pixels), the color format of the device where the image was created, and the resolution of the device used to create that image. A DIB generally has its own color table, and can therefore be displayed on a variety of devices.
device manager
An application, included on all Windows CE-based platforms, that manages stream interface device drivers. The device manager handles loading and unloading stream interface device drivers, identifying the correct driver for plug-and-play devices, managing running device drivers, and notifying stream interface device drivers of power-up and power-down events.
device partnership
A A device partnership is a registry key on a Windows CE device that a desktop computer uses to identify a Windows CE device to which a desktop computer is connected. The key defines values for synchronization, file conversions, and backup-and-restore information, which enable multiple Windows CE devices to connect to the same desktop computer. A device partnership is created the first time you connect a Windows CE device to a desktop computer.
DHCP
See Dynamic Host Configuration Protocol.
dialog box
A temporary window that contains controls. You can use it to display status information and to get user input.
dialog box procedure
An application-defined callback function that the system calls when it has input for a dialog box or has tasks for a dialog box to carry out.
dialog box template
A binary description of a dialog box and the controls it contains. You can create this template as a resource to be loaded from the application’s executable file, or created in memory while the application runs.
DIB
See device-independent bitmap.
digital signature
Binary data attached to a message that uniquely identifies a sender. A digital signature can be used with hash values to ensure that a transmitted message has not been tampered with.
direct cable connection (DCC)
A RAS networking connection between two computers or between a computer and a Windows CE-based device, which uses a serial or parallel cable directly connected between the systems instead of a modem and a phone line.
direct memory access (DMA)
Memory access that does not involve the microprocessor and is frequently used for data transfer directly between memory and an “intelligent” peripheral device, such as a disk drive.
DLL
See dynamic-link library.
drag-and-drop
A technique for moving or copying data between applications, between windows within an application, or within a single window in an application. The user selects the data to be transferred and drags the data to the desired destination. Windows CE supports drag-and-drop operations. However, nondefault drag-and-drop operations, equivalent to right mouse button drag-and-drop operations, are not supported.
drop-down menu
A menu that drops from the menu bar when requested and remains open without further action until the user closes it or chooses a menu item.
dummy file filter
A means for transferring files of nonstandard or possibly unknown extensions for which no translation is necessary. Passing the file through the dummy filter keeps the No Convertor Selected dialog box from being issued to the user.
dynamic data exchange (DDE)
An interprocess communication method that allows two or more applications running simultaneously to exchange data and commands.
Dynamic Host Configuration Protocol (DHCP)
A TCP/IP protocol that enables a network connected to the Internet to automatically assign a temporary Internet protocol (IP) address to a host when the host connects to the network.
dynamic-link library (DLL)
A set of autonomous functions that any application can use. DLLs are a set of source code modules with each module containing a set of functions.


E

edit control
A rectangular window in which a user can enter and edit text from the keyboard. An edit control is also referred to as a text box..
embedded
Software code or commands built into their carriers. For example, application insert embedded printing commands into a document to control printing and formatting. Low-level assembly is embedded in higher-level languages, such as C, to provide more capabilities or better efficiency.
encryption
The process of transforming data into a form unreadable by anyone without a secret key.
environment variable
An element of the operating system environment, such as a path, a directory name, or a configuration string. Environment variables are typically set within batch files.
Ethernet
A widely used LAN developed by Xerox, Digital, and Intel. Ethernet networks connect up to 1,024 nodes at 10 megabits per second over twisted pair, coax, and optical fiber.
event
An event is an occurrence that triggers a notification. Windows CE supports timer and system events.
event-driven operating system
An operating system that constantly evaluates and responds to sets of events, such as key presses or mouse movements.
event object
A synchronization object that enables one thread to notify another that an event has occurred. Event objects are useful when a thread needs to know when to perform its task. For example, a thread that copies data to an archive needs to be notified when new data is available. By using an event object to notify the copying thread of the availability of new data, the thread can perform its task as soon as possible.
exception handling
The process of dealing with exceptions, or errors, as they arise during application execution. Exceptions occur when an application executes abnormally due to conditions outside the application’s control. Windows CE does not support C++ exception handling.
execute in place (XIP)
The process of executing code directly from read-only memory (ROM), rather than loading it from random access memory (RAM) first. Executing the code in place, instead of copying the code into RAM for execution, saves system resources. Applications in other file systems, such as on a PC Card storage device, cannot be executed in this way.
Exit button
A hardware navigation control that functions as the esc key on a keyboard.
extension key
An entry in the registry, corresponding to the extension of a given file, that specifies which file filter will handle conversions for that file type.


F

FAT
See file allocation table.
file allocation table (FAT)
A table or list maintained by some operating systems to manage disk space used for file storage. Files on a disk are stored, as space allows, in fixed-size groups of bytes (characters) rather than from beginning to end as contiguous strings of text or numbers. A single file can thus be scattered in pieces over many separate storage areas. A file allocation table maps available disk storage space so that it can mark flawed segments that should not be used and can find and link the pieces of a file. In MS-DOS, the file allocation table is commonly known as the FAT.
file filter
A Windows CE dynamic-link library (DLL) that controls the transfer of data between a desktop computer and a Windows CE-based device.
file handle
A token, or number, that the operating system uses to identify or refer to an open file or, sometimes, to a device.
file pointer
The offset into an open file that the operating system maintains internally. It points to the starting location where data is read from or written to when a read or write operation is performed on an open file. The file pointer can be moved to any location within the file by a seek operation.
file system
In an operating system, the overall structure in which files are named, stored, and organized. A file system consists of files, directories, and the information needed to locate and access these items. The term can also refer to the portion of an operating system that translates requests for file operations from an application into low-level, sector-oriented tasks that can be understood by the drivers controlling the disk drives.
file system application interface
A subset of the standard Win32 file system functions. These functions let you create directories and data files, read and write file data, and retrieve file and directory information.
file system driver (FSD)
A user-written DLL that the operating system loads to interface to a user-created installable file system. The functions that access an installable file system are implemented in the DLL. See also installable file system.
FSD
See file system driver.
File Transfer Protocol (FTP)
The protocol used for copying files to and from remote computer systems on a network using a Transmission Control Protocol/Internet Protocol (TCP/IP), such as the Internet. This protocol also allows users to use FTP commands to work with files, such as listing files and directories on the remote system.
firmware
Software routines stored in read-only memory (ROM). Unlike random access memory (RAM), read-only memory stays intact even in the absence of electrical power. Startup routines and low-level input/output instructions are stored in firmware. It falls between software and hardware in terms of ease of modification. See also RAM, ROM.
flash card
See PC Card storage device.
flash memory
Semiconductor memory that can operate as ROM but, on an activating signal, can rewrite its contents as though it were RAM.
flash time
The elapsed time, in milliseconds, required to display, invert, and restore the caret display. This value is twice as much as the blink time.
focus window
The window that is currently receiving keyboard input. The focus window is always the active window, a descendent of the active window, or NULL.
foreground thread
The thread used to create the window with which the user is currently working.
foreground window
The window with which the user is currently working. The system assigns a slightly higher priority to the thread used to create the foreground window than it does to other threads.
free threading model
A model in which an object can be used on any thread at any time. See apartment model threading and single threading model.
FTP
See File Transfer Protocol.


G

GDI
See graphics device interface.
globalization
The process of developing an application core whose feature and code designs do not make assumptions based on a single language or locale, and whose source code simplifies the creation of different language editions of an application.
global variable
A variable whose value can be accessed and modified by any statement in an application, not merely within a single routine in which it is defined.
graphics object
The pen, brush, bitmap, palette, region, font, and path associated with a device context. Windows CE does not support paths.
graphics device interface (GDI)
The Windows CE subsystem responsible for displaying text and images on display devices and printers. The GDI processes graphical function calls from a Windows-based application. It then passes those calls to the appropriate device driver, which generates the output on the display hardware. By acting as a buffer between applications and output devices, the GDI presents a device-independent view of the world for the application while interacting in a device-dependent format with the device. Because of the smaller memory footprint of Windows CE-based devices, Windows CE supports only a subset of the standard Win32 GDI.
Graphics, Windowing, and Events Subsystem (GWES)
The Windows CE module that contains the graphics and windowing functionality needed to display text and images and to receive user input. It includes all the functionality needed to create and manage windows, controls, dialog boxes, and resources such as icons and menus. It also processes all user input. GWES includes the graphics device interface, which displays text and images on display devices and printers.
grayscale
A sequence of shades ranging from black through white, used in computer graphics to add detail to images or to represent a color image on a monochrome output device. Like the number of colors in a color image, the number of shades of gray depends on the number of bits stored per pixel. Grays may be represented by actual gray shades, by halftone dots, or by dithering.
gripper bar
A gripper bar is a tall, thin rectangle with a dark stripe running through it that appears on a rebar or a command band control. By touching and dragging a gripper bar with a stylus, a user can reposition a rebar or command bar. Gripper bars are especially useful for bringing off-screen rebar or command bar controls into view.
group box
A rectangular area within a dialog box in which you can group together other controls that are semantically related. The controls are grouped by drawing a rectangular border around them. Any text associated with the group box is displayed in its upper-left corner.
GUID
A globally unique identifier. See Universally Unique Identifier.
GWES
See Graphics, Windowing, and Events Subsystem.


H

handle
1. A pointer to a pointer; that is, a variable that contains the address of another variable, which in turn contains the address of the desired object. In certain operating systems, the handle points to a pointer stored in a fixed location in memory, whereas that pointer points to a movable block. If applications start from the handle whenever they access the block, the operating system can perform memory management tasks such as garbage collection without affecting the applications. 2. Any token that an application can use to identify and access an object such as a device, a file, a window, or a dialog box. 3. One of several small squares displayed around a graphical object in a drawing application. The user can move or reshape the object by clicking on a handle and dragging.
hash value
A value used in creating digital signatures. This value is generated by imposing a hashing algorithm onto a message. This value is then transformed, or signed, by a private key to produce a digital signature. Also called message digest.
hashing algorithm
A formula used to generate hash values and digital signatures.
H/PC
A Handheld PC.
header control
A horizontal window that is usually positioned above columns of data. It is divided into partitions that correspond to the columns, and each partition contains the title for the column below it.
heap
A portion of memory reserved for an application to use for the temporary storage of data structures whose existence or size cannot be determined until the application is running. The application can request free memory from the heap to hold such elements, use it as necessary, and later free the memory.
hibernation
The way in which a Windows CE-based device manages a memory shortage by requesting applications to free memory that is not currently needed.
hibernation threshold
The point at which the system enters a limited memory state.
high-resolution performance counter
Hardware that provides high-resolution timing useful in improving the performance of applications.
hook
A location in a routine or application in which the programmer can connect or insert other routines for the purpose of debugging or enhancing functionality.Windows CE does not support hooking.
hot key
A keystroke or combination of keystrokes that switches the user to a different application, often a terminate-and-stay-resident (TSR) application or the operating system user interface. Hot keys generate a WM_HOTKEY message.
hot spot
The pixel in a cursor that marks the exact screen location affected by a mouse or pen action, such as a button click. Messages include the coordinates of a hot spot.
HTML
See Hypertext Markup Language.
Hypertext Markup Language (HTML) viewer control
A control that provides programmers with the ability to implement the Windows CE Pocket Internet Explorer and the Help engine. It also provides independent software vendors (ISVs) with the ability to implement additional viewers based on the HTML viewer control.
HTTP
See Hypertext Transfer Protocol.
Hypertext Markup Language (HTML)
A markup language derived from the Standard Generalized Markup Language (SGML). Used to create a text document with formatting specifications that tells a software browser how to display the page or pages included in the document.
Hypertext Transfer Protocol (HTTP)
The client/server protocol used to access information on the Web.


I

IAS
See Information Access Service.
ICMP
See Internet Control Message Protocol.
icon
A small image displayed on the screen to represent an object that can be manipulated by the user. By serving as visual mnemonics and allowing the user to control certain computer actions without having to remember commands or type them at the keyboard, icons are a significant factor in the user-friendliness of graphical user interfaces.
IDE
See Integrated Development Environment
idle priority
One of three thread priority groups. Idle priority indicates that a thread’s processing can wait until all other threads have finished running.
IEEE
See Institute of Electrical and Electronics Engineers.
IHV
See independent hardware vendor.
IM
See input method.
image list
A collection of images that are all the same size, such as bitmaps or icons.
Inbox
A mail client application provided with Windows CE.
independent hardware vendor (IHV)
A company that manufactures devices that connect to Windows CE-based platforms, such as PC Card storage devices. IHVs must also produce stream interface device drivers for their devices. See stream interface device driver.
.inf
A CAB Wizard input file that specifies information about the application.
Information Access Service (IAS)
A part of an IrDA infrared communication protocol used so that devices can learn about the services offered by another device.
infrared (IR)
Of or relating to the range of invisible radiation wavelengths from about 750 nanometers, just longer than red in the visible spectrum, to 1 millimeter, on the border of the microwave region.
Infrared Data Association (IrDA)
The industry organization of computer, component, and telecommunications vendors who have established the standards for infrared communication between computers and peripheral devices such as printers. Windows CE supports the IrDA standard through the Winsock application programming interface (API). Windows CE-based applications that communicate over serial cables using the Winsock API will communicate over IrDA-compliant IR links with only minimal reprogramming.
Infrared Link Management Protocol (IrLMP)
A service multiplexing protocol that provides for multiple sessions over a single point-to-point link.
Infrared Sockets (IrSock)
An implementation of the Winsock protocol.
.ini
An initialization file that registers an application with an application manager. It contains information such as the location of .cab files, icon files, and the installation directory.
initialization vector
A random number used as a starting point when encrypting data. Two identical packets of data encrypted with the same key can result in two different packets of ciphertext, if each packet of data is encrypted with different initialization vectors.
input method
A COM component that allows the user to input text using a touch screen. For example, a Palm-size PC supports input methods for a graphical representation of a keyboard and a character recognizer.
input panel
A window control that supports various input methods, such as writing or drawing.
installable device driver
See stream interface device driver.
installable file system
A file system that is accessed through a file system driver (FSD) that is loaded onto a device by the user, rather than accessed by the built-in file system. An installable file system may be implemented differently from the built-in file system. For example, an installable file system may prevent a user from deleting files, provide automatic compression of files, or use a structure for internal information different from that used by the built-in file system. See also file system driver.
installable file system driver
See file system driver.
Institute of Electrical and Electronics Engineers (IEEE)
An organization of engineering and electronics professionals notable for developing standards for hardware and software.
integrated development environment (IDE)
In the Microsoft Developer Studio, an integrated set of Windows-based tools for building, testing, and refining an application . The IDE includes a variety of editors, project build facilities, compilers, an incremental linker (for C++), a class viewer, and an integrated debugger. The IDE enables you to create, test, and refine your applications and Web sites all in one place.
interface
1. The point at which a connection is made between two elements so that they can work with one another. 2. Software that enables an application to work with the user (the user interface, which can be a command-line interface, menu-driven, or a graphical user interface), with another application, such as the operating system, or with the computer’s hardware. 3. A card, plug, or other device that connects pieces of hardware with the computer so that information can be moved from place to place. 4. A networking or communications standard that defines ways for different systems to connect and communicate.
Internet Control Message Protocol (ICMP)
A network-layer Internet protocol that provides error correction and other information relevant to Internet Protocol (IP) packet processing, such as testing whether a particular computer is connected to the Internet (“pinging”), by sending a packet to its IP address and waiting for a response. For example, it can let the IP software on one machine inform another machine about an unreachable destination. See ping.
Internet Information Server (IIS)
Microsoft's brand of Web server software, using Hypertext Transfer Protocol (HTTP) to deliver World Wide Web documents. It incorporates various functions for security, allows for CGI applications, and also provides for Gopher and File Transfer Protocol (FTP) servers.
Internet Protocol (IP)
Provides the protocol for connecting hosts over a network, breaking messages into packets, addressing the packets, routing them from the sender to the destination network, and reassembling the packets into the original message at the destination. IP corresponds to the network layer in the International Organization for Standardization Open Systems Interconnection (ISO/OSI) model.
Internet Protocol (IP) address
A 32-bit (4-byte) binary number that uniquely identifies a host computer connected to the Internet to other Internet hosts, for the purposes of communication through the transfer of packets. An IP address is expressed in “dotted quad” format, consisting of the decimal values of its four bytes, separated with periods; for example, 127.0.0.1. The first one, two, or three bytes of the IP address, assigned by InterNIC Registration Services, identify the network the host is connected to; the remaining bits identify the host itself.
interrupt
A request for attention from the processor. When the processor receives an interrupt, it suspends its current operations, saves the status of its work, and transfers control to a special routine known as an interrupt handler, which contains the instructions for dealing with the particular situation that caused the interrupt. Interrupts can be generated by various hardware devices to request service or report problems, or by the processor itself in response to application errors or requests for operating-system services. Interrupts are the processor’s way of communicating with the other elements that make up a computer system. A hierarchy of interrupt priorities determines which interrupt request will be handled first if more than one request is made. An application can temporarily disable some interrupts if it needs the full attention of the processor to complete a particular task.
interrupt identifier (interrupt ID)
A unique value used by the kernel to identify the device that raised the interrupt and that requires more processing. The kernel then uses the interrupt identifier to indicate whether all handling is complete, or whether to launch an interrupt service thread that handles further processing by the device driver.
interrupt priority
One of three thread priority groups. Interrupt priority is reserved for operating system threads.
interrupt request line (IRQ)
A hardware line over which a device, such as an I/O port, keyboard, or disk drive, can send interrupt requests to the CPU. Interrupt request lines are built into the computer’s internal hardware and are assigned different levels of priority so that the CPU can determine the sources and relative importance of incoming service requests.
interrupt service routine (ISR)
A small subroutine that resides in the OEM adaptation layer. The ISR executes in kernel mode and has direct access to the hardware registers. Its sole job is to determine what interrupt identifier to return to the interrupt support handler. Essentially, ISRs map physical interrupts onto logical interrupts.
interrupt service thread (IST)
A thread created by a device driver to wait on an event.
interrupt support handler
A routine that registers a driver so that it can handle a particular interrupt and unregister it later. It also enables communication between the interrupt service routine, interrupt service thread, and subroutines within the OEM Adaptation Layer (OAL).
I/O
Input/output.
IP
See Internet Protocol.
IrCOMM
An infrared implementation of the serial line communication driver. IrCOMM is supported by Windows CE.
IrDA
See Infrared Data Association.
IrLMP
See Infrared Link Management Protocol.
IrLPT
A protocol for printing through a serial infrared connection.
IRQ
See interrupt request line.
IrSock
See Infrared Sockets.
ISR
See interrupt service routine.
ISV
Independent software vendor.
item script
An application written in HTML and Visual Basic Script, JScript, Java Script, or other scripting languages that specifies the behavior of an item within a channel.


K

kernel
The main module of the Windows CE operating system. The kernel provides system services for managing threads, memory, and resources.
key
A field or expression used to identify a record; often used as the index field for a database table.
key binary large object (key BLOB)
A key BLOB provides a way to store keys outside of the cryptographic service provider (CSP) and are used to transfer keys securely from one CSP to another. A key BLOB consists of a standard header followed by data representing the key.
key container
A place where cryptographic key pairs are stored. Each key container stores all of the key pairs belonging to a specific user.
keyboard accelerator
1. In applications, a key or key combination used to perform a defined function. Also called shortcut key. 2. In hardware, a device that speeds or enhances the operation of one or more subsystems, leading to improved application performance.


L

LAN
See local area network.
launch entry
A registry entry that specifies the order in which applications launch.
layered device driver
A sample device driver that comes with the Embedded Toolkit. It contains two layers: a model device driver (MDD) layer and a platform-dependent driver (PDD) layer. See model device driver and platform-dependent driver.
list box
A control that enables the user to choose one option from a list of possibilities. The list box appears as a box, displaying the currently selected option, next to a button marked with a down-arrow. When the user clicks or taps on the button, the list appears. The list has a scroll bar if there are more options than the list has room to show.
list view
A common control that displays a collection of items, such as files or folders. Each item has an icon and a label.
load file
A file that contains a list of commands for the Load function to process. You use load file commands to direct Ppcload.dll to create directories on a Windows CE-based device, copy files into the directories, edit registry entries, execute applications on the Windows CE-based device, and add items to the unload script. The fully qualified path name of the load file is given as a command line argument to Load.
local area network (LAN)
A group of computers and other devices dispersed over a relatively limited area and connected by a communications link that enables any device to interact with any other on the network. LANs commonly include microcomputers and shared resources such as laser printers and large hard disks. The devices on a LAN are known as nodes, and the nodes are connected by cables through which messages are transmitted.
localization
The process of adapting an application for a specific international market, which includes translating the user interface, resizing dialog boxes, customizing features if necessary, and testing results to ensure that the application still functions properly.
logical palette
An array of colors, or color palette, that an application creates and associates with a device context and uses for graphics output.


M

main priority
One of three thread priority groups. Main is the default priority.
main window
The window that serves as the primary interface between the user and an application.
MDD
See model device driver.
MDI
See multiple-document interface.
menu
A list of options from which a user can make a selection in order to perform a desired action, such as choosing a command or applying a particular format to part of a document. Many applications, especially those that offer a graphical interface, use menus as a means of providing the user with an easily learned, easy-to-use alternative to memorizing commands and their appropriate usage.
menu handle
A unique value of type HMENU used to identify a menu.
menu item
A string or bitmap displayed in a menu. Choosing a menu item either sends a command message or activates a pop-up menu.
menu template
A menu template defines a menu, including the items on a menu bar and all submenus.
message
A structure or set of parameters used for communicating information or a request. Messages can be passed between the operating system and an application, different applications, threads within an application, and windows within an application.
message box
A secondary window that is displayed to inform a user about a particular condition.
message digest
See hash value.
message identifier
A unique value that identifies a message. System-defined messages use named constants, such as WM_PAINT, as message identifiers. Windows CE reserves message-identifier values in the range 0x0400 through 0x7FFF for application-defined messages.
message queue
An ordered list of messages awaiting transmission, from which they are taken up on a first-in, first-out (FIFO) basis.
message store
The database in the object store for storing mail messages.
MFC
See Microsoft Foundation Classes.
Microsoft Foundation Classes (MFC)
The C++ class library that Microsoft provides with its C++ compiler to assist programmers in creating Windows-based applications. MFC hides the fundamental Windows API in class hierarchies so that programmers can write a Windows-based application without needing to know the details of the native Windows API.
Mobile Channels
A Windows CE technology that represents a fourth type of Internet Explorer 4.0 (IE4) channel to allow the user to access the Web with great mobility.
modal dialog box
A modal dialog box requires the user to supply information or cancel the dialog box before allowing the application to continue.
model device driver (MDD)
The platform-neutral layer of a native device driver supplied by Microsoft. See native device driver.
modeless dialog box
A modeless dialog box allows the user to supply information and return to a previous task without closing the dialog box.
module
A subset of the Windows CE operating system. Windows CE is structured as a collection of modules. Each module is a self-contained subset of the Windows CE operating system that can be used to construct a customized operating system for a particular device.
monolithic device driver
A sample device driver that comes with the Microsoft Windows CE Platform Builder.
month calendar control
A child window that displays a monthly calendar. The calendar can display one or more months at a time.
mounted file system
A file system located on a removable medium, such as a PC Card storage device. The operating system loads, or mounts, the file system when the medium is inserted into the device. It unloads, or unmounts, the file system either when the medium is removed or when the user issues a command to do so.
multimedia driver
A device driver that uses the Windows CE subset of the Win32 WDM device driver model.
multiple-document interface (MDI)
A user interface in an application that allows the user to have more than one document open at the same time. MDI is not supported by Windows CE.
mutex object
An interprocess synchronization object whose state is set to signaled when it is not owned by any thread, and nonsignaled when it is owned. Only one thread at a time can own a mutex.


N

NaN
Not a number.
national language support (NLS)
A function which enables you to specify system and user locale information.
native device driver
A software component that enables a computer system to communicate with a device. In Windows CE, a native device driver is linked with the GWES component. The driver consists of a model device driver (MDD) layer and a platform dependent driver (PDD) layer. Together, these layers make it possible for applications to access physically different, but functionally equivalent, hardware resources in the same way on all Windows CE-based platforms. Replaces the term built-in device driver.
navigation control
On a device, a moveable piece, such as wheel or key, that sends virtual key codes. These virtual key codes are often used to move the cursor, alter the view of the current document, or launch a new application. A navigation control usually exists on a device that does not require a full hardware keyboard, such as a Palm-size PC.
NDIS driver
A device driver for NDIS network adapters.
NDIS
A programming interface—the network driver interface specification—for different protocols sharing the same network hardware.
network stack
An operating system component responsible for processing data that is transmitted or received over a network.
node
1. In local area networks, a device that is connected to the network and is capable of communicating with other network devices. 2. In tree structures, a location on the tree that can have links to one or more nodes below it. Some authors make a distinction between node and element, with an element being a given data type and a node comprising one or more elements as well as any supporting data structures.
nonclient area
The parts of a window that are not a part of the client area. A window’s nonclient area consists of the border, menu bar, title bar, and scroll bar.
nonsignaled
See synchronization object.
notification
A signal from the operating system that an event has occurred. This could be a timer event or a system event such as establishing a network connection. An application registers a notification for an event and the system generates a notification when the event occurs. Windows CE provides an application programming interface (API) that can be used to register events and select options that determine the type of notification.
notification function
A Windows CE function that allows an application to register its name and an event with the system. When the event occurs the kernel automatically starts the named application.
notification message
A message a control sends to its parent window when events, such as input from the user, occur.


O

object
A file, directory, database, or database record that resides in an object store.
object ID
See object identifier.
object identifier
1. A unique value which identifies each object in the object store. 2. In reference to the Contacts database, an object identifier is a unique value that the system assigns to each address card when it is added. An application uses the object identifier when querying an address card’s properties or when modifying or deleting an address card.
object store
The persistent storage that Windows CE makes available to applications. For example, Windows CE reserves part of its available random access memory (RAM) for the operating system and uses the rest for the object store. This data can be stored in files, registry entries, or in Windows CE databases.
OEM
See original equipment manufacturer.
OEM adaptation layer (OAL)
That portion of Windows CE that must be provided by the hardware manufacture to adapt Windows CE to their platform.
OLE
Object Linking and Embedding. A technology for transferring and sharing information among applications. When an object, such as an image file created with a painting application, is linked to a compound document, such as a spreadsheet or a document created with a word processing application, the document contains only a reference to the object; any changes made to the contents of a linked object will be seen in the compound document. When an object is embedded in a compound document, the document contains a copy of the object; any changes made to the contents of the original object will not be seen in the compound document unless the embedded object is updated. See Automation.
option button
In graphical user interfaces, a means of selecting one of several options, usually within a dialog box. An option button, also known as a radio button, appears as a small circle that, when selected, has a smaller, filled circle inside it. Option buttons act like the station selector buttons on a car radio. Selecting one button in a set deselects the previously selected button, so one and only one of the options in the set can be selected at any given time. In contrast, check boxes are used when more than one option in the set can be selected at the same time.
original equipment manufacturer (OEM)
For Windows CE, an OEM is a company that manufacturers a hardware platform and ports Windows CE to that platform.
overlapped communication operation
The performance of two distinct communication operations simultaneously, for example, a simultaneous read/write operation. Windows CE does not support overlapped communication operation, but does support multiple reads/writes pending on a device.
overlapped window
A window with the WS_OVERLAPPED style. Overlapped Windows are top-level windows designed to serve as an application’s main window.


P

packet
A unit of information transmitted as a whole from one device to another on a network.
parallel port
The input/output connector for a parallel interface device.
parent window
A window that has one or more child windows.
parser
An application that breaks data into smaller chunks so that an application can act upon the information. For example, Mobile Channels use a Channel Definition Format parser to parse a channel.
pASP
See pocket Active Server Pages.
path
1. In communications, a link between two nodes in a network. 2. A route through a structured collection of information, as in a database, an application, or files stored on disk. 3. In programming, the sequence of instructions a computer carries out in executing a routine. 4. In file storage, the route followed by the operating system in finding, sorting, and retrieving files on a disk. 5. In graphics, an accumulation of line segments or curves to be filled or overwritten with text.
PC Card storage device
A trademark of the Personal Computer Memory Card International Association (PCMCIA) that is used to describe add-in cards that conform to the PCMCIA specification. A PC Card storage device is a removable device approximately the same size as a credit card that is designed to plug into a PCMCIA slot. Type I cards are primarily used as memory-related peripherals. Type II cards accommodate devices such as modem, fax, and network cards. Type III cards accommodate devices that require more space, such as wireless communications devices and rotating storage media, including hard disks. Flash card is a general term for PC Card storage device.
PCT
See Program Comprehension Tool.
PDD
See platform dependent driver.
pen
A drawing tool used to draw lines and curves.
persistent object
The Component Object Model (COM) defines standards through which clients can request objects to be initialized, loaded, and saved to and from a data store, such as a flat file, structured storage, or memory. COM objects that adhere to these standards are called persistent objects.
personal information manager (PIM)
An application that usually includes an address book and organizes unrelated information, such as notes, appointments, and names, in a useful way.
phone book
Entries in the Remote Access Service (RAS) phone book contain the information necessary to establish a RAS connection. Unlike Windows NT, which keeps the phone book entries in a file, Windows CE stores these entries in the registry.
PIM
See personal information manager.
ping
A protocol for testing whether a particular computer is connected to the Internet by sending a packet to its Internet Protocol (IP) address and waiting for a response.
plaintext
Data that has not been encrypted.
platform
1. The foundation technology of a computer system. Because computers are layered devices composed of a chip-level hardware layer, a firmware and operating-system layer, and an applications layer, the bottommost layer of a machine is often called a platform. 2. In everyday usage, the type of computer or operating system being used. 3. The hardware upon which an implementation of Windows CE will run. 4. The directory structure containing the hardware-specific files needed to build an implementation of Windows CE.
platform dependent driver (PDD)
The platform-specific layer of a native device driver supplied by an original equipment manufacturer. See native device driver.
platform directory
The root of the directory structure where platform-specific files are stored. Each subdirectory in the platform directory specifies the name of a development workstation.
pocket Active Server Pages (pASP)
A scaled-down version of the Active Server Pages optimized for server-side Mobile Channels scripting.
Point-to-Point Protocol (PPP)
An advanced serial packet protocol commonly used for dial-up connections.
POP3
See Post Office Protocol 3.
pop-up menu
A menu that appears on the screen when a user selects a certain item. Pop-up menus can appear anywhere on the screen, and they generally disappear when the user selects an item in the menu.
pop-up window
A special type of overlapped window typically used for dialog boxes, message boxes, and other temporary windows that appear outside an application’s main window.
Portable Operating System Interface for Computer Environments (POSIX)
An IEEE standard that defines the open systems environment standards for system interfaces, shells, tools, testing, verification, real-time processing, security, system administration, networking, and transaction processing. The standard is based on UNIX system services, but it allows implementation on other operating systems.
position index
An identifier associated with each address card in the Contacts database. The position index indicates the address card’s position relative to the other address cards in the database. A position index is distinct from an object identifier.
POSIX
See Portable Operating System Interface for Computer Environments.
Post Office Protocol 3 (POP3)
A standard protocol for transferring mail messages on demand from a mail server.
PPP
See Point-to-Point Protocol.
predefined control
A control belonging to a window class supplied by Windows CE.
preemptive multitasking
A form of multitasking in which the operating system periodically interrupts the execution of an application and passes control of the system to another waiting application. Preemptive multitasking prevents any one application from monopolizing the system.
principal
An entity recognized by a security system. A principal can be a human user or an autonomous process.
priority class
A range of thread priority levels. Whereas Win32 utilizes four priority classes with seven base priority levels per class, Windows CE has only eight base priority levels. Hence, for processes running under Windows CE, preemption is based solely on the thread’s priority.
priority inheritance
A process by which a thread that is blocking a shared resource needed by a higher priority thread inherits the priority of that higher priority thread in order to free the resource for use by the higher priority thread, thus preventing priority inversion.
priority inversion
Priority inversion is a situation in which higher priority thread A spawns lower-priority thread B to access a shared resource that is already in use by lower-priority thread C with greater priority than thread B, blocking higher-priority thread A. This situation can be averted by a process of priority inheritance.
process
An running application that consists of a private virtual address space, code, data, and other operating-system resources, such as files, pipes, and synchronization objects that are visible to the process. A process also contains one or more threads that run in the context of the process.
program button
On a device, a navigation control that is pressed to launch an application. The program button can also be programmed for additional features, such as creating a new document in an application.
program comprehension tool (PCT)
A software engineering tool that facilitates the process of understanding the structure and/or functionality of computer applications.
program memory
Program memory is used for stack and heap storage for both system and non-system applications. Non-system applications are taken from storage memory, uncompressed, and loaded into program memory for execution.
progress bar
A common control that indicates the progress of a lengthy operation by displaying a colored bar inside a horizontal rectangle. The length of the bar in relation to the length of the rectangle corresponds to the percentage of the operation that is complete.
project
1. The implementation of an instance of Windows CE. 2. The directory structure, under Public, containing files that define which components will be included in an implementation of Windows CE.
property
With respect to the database application programming interface, a property refers to a data item that consists of a property identifier, data type, and value. Windows CE supports several data types such as integer, string, time, and binary large object (BLOB).
property sheet
A type of dialog box that lists the attributes or settings of an object, such as a file, application, or hardware device. A property sheet presents the user with a tabbed, index card–like selection of property pages, each of which features standard dialog box-style controls for customizing parameters.
public-key encryption
An asymmetric scheme that uses a pair of keys for encryption: The public key encrypts data, and a corresponding secret key decrypts it. For digital signatures, the process is reversed: The sender uses the secret key to create a unique electronic number that can be read by anyone possessing the corresponding public key, which verifies that the message is truly from the sender.
push button
A small rectangular control that a user can turn on or off. A push button, also known as a command button, has a raised appearance in its default off state and a depressed appearance when it is turned on.


Q

queued message
A message in a message queue.
QWERTY keyboard
A keyboard layout named for the six leftmost characters in the top row of alphabetic characters on most keyboards—the standard layout of most typewriters and computer keyboards.


R

radio button
See option button.
RAM
See random access memory.
random access memory (RAM)
Semiconductor-based memory that can be read and written by the CPU or other hardware devices.
RAPI
See remote Application Programming Interface.
RAS
See remote access server.
raster font
A font in which each glyph—character or symbol—is of a particular size and style, designed for a specific resolution of device and described as a unique bitmap. There are seven system raster fonts available in several sizes stored in the Windows CE read-only memory (ROM). The built-in fonts are built into the Windows CE operating system. Raster fonts are also known as bitmap fonts and non-scalable fonts.
raw infrared (raw IR)
A method of receiving data through an infrared transceiver. Raw IR treats the IR transceiver like a serial cable and does not process data in any way. The application is responsible for handling collision detection and other potential problems.
read-only memory (ROM)
Any semiconductor circuit serving as a memory that contains instructions or data that can be read but not modified, regardless of whether it was placed there by a manufacturer or by a programming process.
rebar control
A rebar control acts as a container for child windows. A rebar control contains one or more bands. Each band can contain one child window, which can be a toolbar or any other control.
record
A data structure that is a collection of elements, each with its own name and type. The elements of a record represent different types of information and are accessed by name. A record can be accessed as a collective unit of elements, or the elements can be accessed individually. A collection of records is a database. A Windows CE database consists of an arbitrary number of records, where each record consists of one or more properties. Each of the records in a specific database typically contain a similar set of properties. A Windows CE database should not be confused with a full-fledged relational database. It is simply a general-purpose, flexible, structured collection of data.
rectangle
A function that draws a rectangular image.
reentrant code
Code written so that it can be shared by several applications or threads within a single process simultaneously. When code is reentrant, one thread can safely interrupt the execution of another thread, execute its own code, and then return control to the first thread in such a way that the first thread does not fail or behave in an unexpected way.
region
A rectangle, polygon, ellipse, or a combination of two or more of these shapes used by Windows-based applications to define a part of the client area to be painted, inverted, filled with output, framed, or used for hit testing.
registered notification
The state of a user notification from the time CeSetUserNotification is called until the time the user is notified.
registry
A central hierarchical database used to store information necessary to configure the system for applications and hardware devices. The registry contains information—such as the applications installed on the computer and the types of documents each can create, property sheet settings for folders and application icons, what hardware exists on the system, and which ports are being used—which the operating system continually references during operation.
remote access server (RAS)
A Windows NT feature by which a single serial connection provides a remote workstation with host connectivity, NT file services, or Novell file and printing services (NWLink). Windows CE supports the standard Win32 RAS functions; however it allows only one connection at a time. RAS functions can be implemented for direct serial connections or dial-up modem connections. The acronym also stands for Remote Access Service.
remote application programming interface (RAPI)
Enables an application running on a desktop computer to make function calls on a Windows CE-based device. The desktop computer is known as the RAPI client and the Windows CE device is known as the RAPI server. RAPI runs over Winsock and TCP/IP.
resistive touch panel
A transparent, touch-sensitive surface implemented to detect user input. See touch screen.
resource
1. Any part of a computer system or a network, such as a disk drive, printer, or memory, that can be allotted to an application or a process while it is running. 2. An item of data or code that can be used by more than one application or in more than one place in an application, such as a dialog box, a sound effect, or a font in a windowing environment. Many features in an application can be altered by adding or replacing resources without the necessity of recompiling the application from source code. Resources can also be copied and pasted from one application into another, typically by a specialized utility application called a resource editor.
Rich Ink
The underlying technology that enables a user to write and draw on a touch-sensitive screen with a stylus.
rocker switch
A hardware navigation control designed to perform spatial navigation, much like the up arrow key and the down arrow key.
ROM
See read-only memory.
ROM image
Files and binaries as they appear in physical memory as defined by the binary image builder (.bib) file.
router
An intermediary device on a communications network that expedites message delivery. On a single network linking many computers through a mesh of possible connections, a router receives transmitted messages and forwards them to their correct destinations over the most efficient available route. On an interconnected set of local area networks using the same communications protocols, a router serves the somewhat different function of acting as a link between these local area networks, enabling messages to be sent from one network to another.


S

salt value
Random data used to supplement encryption schemes. A salt value allows two identical packets of data to be encrypted into two different packets of ciphertext using the same key by changing the salt value with each packet.
scan code
A code number transmitted to a computer whenever a key is pressed or released. Each key on the keyboard has a unique scan code. This code is not the same as the ASCII code for the letter, number, or symbol shown on the key; it is a special identifier for the key itself and is always the same for a particular key. When a key is pressed, the scan code is transmitted to the computer, where a portion of the read-only memory basic input/output system (ROM BIOS) dedicated to the keyboard translates the scan code into its ASCII equivalent. Because a single key can generate more than one character—lowercase “a” and uppercase “A,” for example—the ROM BIOS also keeps track of the status of keys that change the keyboard state, such as the SHIFT key, and takes them into account when translating a scan code.
script
An application consisting of a set of instructions to an application or utility application. The instructions usually use the rules and syntax of the application or utility.
scripting language
A simple programming language designed to perform special or limited tasks, sometimes associated with a particular application or function. An example of a scripting language is Visual Basic Script.
scroll bar
In some graphical user interfaces, a vertical or horizontal bar at the side or bottom of a display area that can be used with a mouse for moving around in that area. Scroll bars often have four active areas: two scroll arrows for moving line by line, a sliding scroll box for moving to an arbitrary location in the display area, and the gray areas in the scroll bar for moving in of one-window increments.
scrolling menu
A menu with top arrows used to scroll menu items up and down.
secure socket layer (SSL)
A proposed open standard developed by Netscape Communications for establishing a secure communication channel to prevent the interception of critical information, such as credit card numbers. The primary purpose of the SSL is to enable secure electronic financial transactions on the Web, although it is designed to work with other Internet services as well.
security context
The security data relevant to a connection. A security context contains information, such as a session key and the duration of a session. Both the client and server in a communication link must cooperate to create a security context.
security package
A security solution that maps Security Support Provider Interface (SSPI) functions to the security protocols specified in a package.
Security Support Provider (SSP)
A dynamic-link library (DLL) containing common authentication and cryptographic data schemes.
Serial Infrared (SIR)
Part of the basic Infrared Data Association (IrDA) communication protocol, a Serial Infrared physical layer provides for serial infrared links.
serial cable
A cable that connects to a serial port. It is used to transfer information between two devices. See serial port.
serial I/O
A communications channel that transmits data one bit at a time.
serialize
The process of converting an object to a series of bytes for transmission to another device. See deserialize.
serial line Internet protocol (SLIP)
A data link protocol that allows transmission of Internet Protocol (IP) data packets over dial-up telephone connections, thus enabling a computer or a local area network to be connected to the Internet or some other network.
serial port
An input/output location (channel) that sends and receives data to and from a computer’s central processing unit or a communications device one bit at a time. Serial ports are used for serial data communication and as interfaces to peripheral devices, such as mice and printers.
server
1. On a local area network (LAN), a computer running administrative software that controls access to the network and its resources, such as printers and disk drives, and provides resources to computers functioning as workstations on the network. 2. An application that responds to requests from another application or task. See client.
service identifier
An identifier used by a service to uniquely identify messages. This value should be changed only by the service library.
session identifier
An identifier generated by a mail transport service. Each time a Post Office Protocol 3 (POP3) connection is made to the server, the server looks at all of the currently stored messages and assigns a session identifier to each message, numbered 1 through the total number of messages. This makes it easier to reference a particular message without having to use its long unique identifier. The session identifier can be trusted only during a single connection to the mail server.
session key
A key used in symmetric encryption schemes where a single key is used to both encrypt and decrypt data.
SGML
See Standard Generalized Markup Language.
shared directory
On a local area network, a directory on a disk that is located on a computer other than the one the user is operating. A shared directory differs from a network drive in that the user has access to only that directory.
shared library
Any code module that can be accessed and used by many applications. Shared libraries are used primarily for sharing common code between different executable files or for breaking an application into separate components, thus allowing easy upgrades. In Windows CE, shared libraries are usually referred to as dynamic-link libraries (DLLs).
shell
An application that enables the user to connect with the kernel and, thus, the system, usually providing some basic services in addition to facilitating the loading and executing of applications.
sibling window
A child window that has the same parent window as one or more other child windows.
signaled
See synchronization object.
signature file
A file which ensures that a cryptographic service provider (CSP) will be recognized by the operating system.
silkscreen button
A section of a resistive touch panel with a painted icon. An OEM provides a driver that lets this section of the panel send virtual key messages. A silkscreen button is considered a navigation control.
silkscreen region
A section of a resistive touch panel that contains several silkscreen buttons.
Simple Mail Transfer Protocol (SMTP)
A TCP/IP protocol for sending messages from one computer to another on a network. This protocol is used on the Internet to route e-mail. See also TCP/IP.
single threading model
A model in which all objects are executed on a single thread. Contrast multithreaded application; see free threading model, apartment model threading.
SIR
See Serial Infrared.
SLIP
See serial line Internet protocol.
SMTP
See Simple Mail Transfer Protocol.
socket
An object that represents an endpoint for communication between processes across a network transport. Sockets have a datagram or stream type and can be bound to a specific network address. Windows Sockets provides an application programming interface (API) for handling all types of socket connections in Windows.
sort order
The order in which a set of records or other data objects are to be sorted, or the function that defines this order. Possible sort orders for an array of strings, for example, could include alphabetical order or ascending order by length.
spin button
A control containing a pair of arrow buttons that a user can tap with the stylus to increment or decrement a value. A spin button control is most often used with a companion control, called a buddy window, in which a current value is displayed. Also called up-down control.
SSL
See secure socket layer.
stack
A region of reserved memory in which applications store status data such as procedure and function call addresses, passed parameters, and sometimes local variables.
Standard Generalized Markup Language (SGML)
An information-management standard adopted by the International Organization for Standardization (ISO) in 1986 as a means of providing platform- and application-independent documents that retain formatting, indexing, and linked information. SGML provides a grammar-like mechanism for users to define the structure of their documents, and the tags they will use to denote the structure in individual documents.
static control
A control used to display text, to draw frames or lines separating other controls, or to display icons. A static control does not accept user input.
status bar
A space at the bottom of many application windows that contains a short text message about the current condition of the application. Some applications also display an explanation of the currently selected menu command in the status bar.
storage memory
Storage memory is similar to a RAM disk on a desktop computer. It is used to store data and non-system applications.
stream cipher mode
A method of encryption where data is encrypted one bit at a time. See block cipher mode.
stream interface device driver
A user-level DLL that controls devices connected to a Windows CE-based platform. A stream interface device driver presents the services of a hardware device to applications by exposing Win32 stream interface functions. Stream interface drivers also can control devices built into a Windows CE-based platform, depending on the software architecture for the drivers. Replaces the term installable device driver.
stream mode
An asynchronous method of calling CeRapiInvoke by using an IStream type interface to exchange arbitrary-sized data in any order and direction.
stylus
A pointing device used on a touch-sensitive surface.
subfolder
A directory, or logical grouping of related files, within another directory.
submenu
A menu that appears as the result of the selection of an item on another higher-level menu.
symbol
A name that represents a register, an absolute value, or a memory address (relative or absolute).
symmetric encryption
A type of encryption where the same key is used to encrypt and decrypt data.
sysgen phase
Refers to the process of defining and building the selected modules and components, as governed by the Makefile located in the directory %_PUBLICROOT%\Common\Cesysgen.
system-defined message
A message the system uses to control the operations of an application and to provide input and other information for an application to process. An application can also send or post a system-defined message. An application generally uses this message to control the operation of control windows created by using preregistered window classes.
system registry functions
The functions used to manipulate keys and values in the registry. A Windows CE-based application uses the standard Win32 registry functions.
synchronization
The process of updating information between the desktop computer and a Windows CE-based device to ensure that data is the same on both computers.
synchronization object
An object whose handle can be specified in one of the wait functions to coordinate the execution of multiple threads. A synchronization object will be a member of one of the synchronization classes. Synchronization classes are used when access to a resource must be controlled to ensure integrity of the resource. The state of a synchronization object is either signaled, which can allow the wait function to return, or nonsignaled, which can prevent the function from returning. More than one process can have a handle of the same synchronization object, making interprocess synchronization possible. There are four types of synchronization objects: mutex, semaphore, event and critical section. Of these, Windows CE supports mutex, event, and critical section.
synchronous operation
1. Two or more processes that depend upon the occurrences of specific events such as common timing signals. 2. Data transmission method in which there is constant time between successive bits, characters, or events. The timing is achieved by the sharing of a single clock. Each end of the transmission synchronizes itself with the use of clocks and information sent along with the transmitted data. Characters are spaced by time, not by start and stop bits. 3. A function call that blocks execution of a process until it returns. Compare asynchronous operation.


T

tab control
A control that is analogous to a set of dividers in a notebook or labels in a file cabinet. A tab control is used in a property sheet to provide a way for a user to move from one property page to another.
TAPI
See Telephony Application Programming Interface.
target platform
The system for which Windows CE is being adapted.
TCP/IP
See Transmission Control Protocol/Internet Protocol.
telephony
Telephone technology; the conversion of sound into electrical signals, its transmission to another location, and its reconversion to sound, with or without the use of connecting wires.
Telephony application programming interface (TAPI)
A set of functions in the Win32 API that lets a computer communicate directly with telephone systems. Windows CE supports TAPI version 1.5. It provides the basic functions, structures, and messages for establishing outgoing calls and controlling modems from a Windows CE-based device.
Telephony Service Provider Interface (TSPI)
The Windows CE TSPI defines the external interface of a service provider to be implemented by vendors of telephony equipment. A telephony service provider accesses vendor-specific equipment through a standard device driver interface. Installing a service provider allows Windows CE-based applications that use elements of telephony to access the corresponding telephony equipment.
TEXT
A Win32 macro that exists so that code can be compiled either as American Standard Code for Information Interchange (ASCII) text or as Unicode. For Windows CE, which supports only Unicode, the macro forces the compiler to convert ASCII characters to Unicode characters. For example, passing the ASCII string “Hello Windows CE!’’ through the TEXT macro converts all characters in the string to 16-bit wide characters.
thread
A process that is part of a larger process or application. A thread can execute any part of an application’s code, including code that is currently being executed by another thread. All threads share the virtual address space, global variables, and operating-system resources of their respective processes.
thread identifier
The unique identifier associated with a specific thread. Note that thread identification numbers are reused; they identify a thread only for the lifetime of that thread.
thread local storage (TLS)
A Win32 mechanism that allows multiple threads of a process to store data that is unique for each thread. For example, a spreadsheet application can create a new instance of the same thread each time the user opens a new spreadsheet. A dynamic-link library that provides the functions for various spreadsheet operations can use thread local storage to save information about the current state of each spreadsheet.
thread synchronization
The method used to coordinate the execution of two or more threads. There are two states in synchronization, signaled and non-signaled. Threads can either modify the state of the synchronization object or wait for the object to reach a signaled state.
time-out value
A specified time interval used by a timer. Each time the time-out value elapses, Windows CE sends a WM_TIMER message to the window associated with the timer.
timer
An internal routine that causes the system to send a WM_TIMER message whenever a specified interval elapses.
timestamping
The process of attaching the date and time to a message.
TLB
See Translation Look-aside Buffer.
TLS
See thread local storage.
toolbar
A row, column, or block of on-screen buttons or icons. When these buttons or icons are depressed, macros or certain functions of the application are activated.
ToolTip
A small rectangular pop-up window that displays a brief description of a command bar button’s purpose.
top-level window
A window that has no parent window.
topmost window
A window with the WS_EX_TOPMOST style. A topmost window overlaps all other non-topmost windows.
touchpad
An input device that functions like a mouse to control cursor movements.
touch panel
See touch screen.
touch screen
A computer screen on which the user selects options, such as from a menu, by touching the screen. The touch screen is composed of a LCD and a resistive touch panel.
trackbar
A common control, also known as a slider control, that consists of a bar with tick marks on it and a slider, also known as a thumb. When a user drags the slider or clicks on either side of it, the slider moves in the appropriate direction, tick by tick.
translation look-aside buffer (TLB)
A table used in a virtual memory system, which lists the physical address page number associated with each virtual address page number. A TLB is used in conjunction with a cache whose tags are based on virtual addresses. The virtual address is presented simultaneously to the TLB and to the cache so that cache access and virtual-to-physical address translation can occur simultaneously.
Transmission Control Protocol/Internet Protocol (TCP/IP)
A protocol developed by the Department of Defense for communications between computers. It is built into the UNIX system and has become the de facto standard for data transmission over networks, including the Internet.Transport and address protocols; TCP is used to establish a connection for data transmission, and IP defines the method for sending the data in packets.
transport functions
A set of functions, exported by a mail transport service dynamic-link library, that are used to transfer mail messages from one location to another.
tree view control
A hierarchical display of labeled items. The top item in the hierarchy is called the root. If an item has other items below it in the hierarchy, it is also referred to as a parent. Items subordinate to parents are called children. Child items, when displayed, are indented below their parent item. The hierarchy may be expanded or collapsed at any level to display or hide child items.
TrueType
A scalable outline font whose glyphs are stored as a collection of line and curve commands, plus a collection of hints.
TSP
A telephony service provider.
TSPI
See Telephony Service Provider Interface.


U

UNC
See universal naming convention.
Unicode
A 16-bit character set capable of encoding almost all known characters and used as a worldwide character-encoding standard. Windows CE uses Unicode exclusively at the system level.
Uniform Resource Locator (URL)
The address of a resource on the Internet. URL syntax is in the form protocol://host/localinfo, where protocol specifies the means of returning the object, such as Hypertext Transfer Protocol (HTTP) or File Transfer Protocol (FTP). Host specifies the remote location where the object resides, and localinfo is a string—often a file name—passed to the protocol handler at the remote location. Also called Universal Resource Locator, Uniform Resource Identifier (URI).
Unimodem
1. The universal modem driver, provided with Windows CE, that translates Telephony Service Provider Interface (TSPI) calls into AT commands, and sends the commands to a virtual device driver that talks to the modem. 2. A universal modem that supports standard modem AT commands. Windows CE currently supports only PCMCIA modems.
uniform naming convention (UNC)
The system of naming files among computers on a network so that a file on a given computer will have the same pathname when accessed from any of the other computers on the network. For example, if the directory c:\path1\path2\...pathn on computer servern is shared under the name pathdirs, a user on another computer would open \\servern\pathdirs\filename.ext to access the file c:\path1\path2\...pathn\filename.ext on servern. See also URL.
universally unique identifier (UUID)
A 128-bit value that uniquely identifies objects such as OLE servers, interfaces, manager entry-point vectors, and client objects. Universally unique identifiers are used in cross-process communication, such as remote procedure calling (RPC) and OLE. Also called globally unique identifier (GUID).
universal serial bus (USB)
A serial bus with a bandwidth of 1.5 megabits per second (Mbps) for connecting peripherals to a microcomputer. USB can connect up to 127 peripherals, such as external CD-ROM drives, printers, modems, mice, and keyboards, to the system through a single, general-purpose port. This is accomplished by daisy chaining peripherals together. USB supports hot plugging and multiple data streams. Developed by Intel, USB competes with DEC's ACCESS.bus for lower-speed applications.
up-down control
See spin button.
URL
See Uniform Resource Locator.
USB driver
A device driver for USB compatible devices.
user level driver
See stream interface device driver.
user notification
A warning to the user that a timer event has occurred. The notification may require the user to perform some action to handle the notification or may generate a sound to alert the user. For example, the system may display a dialog box and play a sound or icon before a scheduled appointment. The user would tap the dialog box OK button to acknowledge the appointment. User notifications are always associated with an application.
UUID
See universally unique identifier.


V

virtual key code
A device-independent value that identifies the purpose of a keystroke as interpreted by the Windows keyboard device driver.


W

wait function
Allows a thread to block its own execution. Wait functions do not return until the specified criteria have been met. The type of wait function determines the set of criteria used. When a wait function is called, it checks whether the wait criteria have been met. If the criteria have not been met, the calling thread enters an efficient wait state, consuming very little processor time while waiting for the criteria to be met. Windows CE supports only single object wait functions.
warm boot
The restarting of a running computer without first turning off the power. Also called soft boot, warm start. Compare cold boot.
.wav
The file extension that identifies sound files stored in waveform (WAV) audio format.
Web Browser ActiveX control
An ActiveX control that programmers can use to add Internet browsing capabilities to applications.
Win32
The application programming interface in Windows 95, Windows NT, and Windows CE that enables applications to use the 32-bit instructions available on 80386 and higher processors.
window
A rectangular area on the screen where an application displays output and receives user input. On a Windows CE-based device that supports a graphical display, a window, rather than the screen itself, is the primary output device. Windows are also the means by which applications send and receive messages to the operating system. Therefore, all Windows CE-based applications—even those that lack a visual interface—need to create and manage windows.
window class
A set of attributes that Windows CE uses as a template to create a window. Each window class has a window procedure that processes messages for all windows of that class. Every window in a Windows CE-based application is a member of a window class.
window control
A predefined child window used in conjunction with another application window to provide a standardized way for users to make selections, carry out commands, and perform input and output tasks. Windows controls typically send WM_COMMAND messages.
window coordinate
The position of a window in relation to the upper-left corner of the screen or, for a child window, the upper-left corner of the parent window’s client area.
window handle
A 32-bit value, assigned by Windows CE, that uniquely identifies a window.
window procedure
A function, called by the operating system, that controls the appearance and behavior of its associated windows. The procedure receives and processes all messages to these windows.
window style
A named constant that defines an aspect of the window’s appearance and behavior not specified by the window’s class.
Windows CE Services
A set of technologies that makes Windows CE-based devices Web-enabled. Its architecture is based on a multi-layered client/server model that provides the functionality to deliver Web content information to Windows CE-based devices from a wireless network or by desktop synchronization.
WinInet function
Win32 Internet function (WinInet) that assists you in adding Internet access to your applications using Hypertext Transfer Protocol (HTTP) and File Transfer Protocol (FTP).
Windows Sockets (Winsock)
A programming interface used to provide a protocol-independent transport interface. Windows CE supports most of the common Winsock functions.
Winsock
See Windows Sockets.
Wireless Push Server (WPS)
See Wireless Services server component.
Wireless Services client component
Decodes and processes messages from the Wireless Push Server (WPS). The client architecture allows for new decoding components to be installed at any time.
Wireless Services server component
Allows a content provider or carrier to configure and schedule any number of information acquisition/encoding/transmission components to create a data stream to be transmitted by a carrier to the device. The server component builds on an open architecture to allow new server components to be installed in any part of the stream at any time.
WPS
See Wireless Push Server.


X

X.509
An international message-handling standard for message authentication and encryption. X.509 is published by the Internal Telecommunications Union (ITU), formerly the International Telegraph and Telephone Consultative Committee (CCITT) standards body.
XIP
See execute in place.


Z

z-order
A stack of overlapping windows. Each window has a unique position in the z-order.


(Untitled)

Topic Not Found

No matching topic was found. Some possible reasons and remedies are as follows:

Reason | Remedy
Destination topic not in the selected active subset. | Set active subset to an appropriate subset or to (Entire Collection).
Link is out-of-date. | Use the index or search function instead.
Link was authored incorrectly. | Use the index or search function instead.
Problems with the MSDN installation. | Uninstall the MSDN Library and then rerun Setup.

See MSDN Library Help for more information on subsets, search, the index, and installation.